1

I am trying to remove the fields from FieldSelector which I have already used.

I have a custom component for FieldSelector lets say A, B, C, D, E are the total fields that are available in FieldSelector component. If I have used A & B, these should not be included in new Rule when Add Rule button is clicked. Upon adding new Rule, Field selector for new rule should only have C, D, E options.

How to achieve this?

This is my code.

        <QueryBuilderAntD>
          <QueryBuilder
            fields={fields}  // All fields are being passed here e.g. A, B, C, D, E
            query={query}
            listsAsArrays
            combinators={[{ name: 'and', label: 'AND' }]}
            validator={defaultValidator}
            controlElements={{
              addGroupAction: () => null,
              addRuleAction: AddRuleAction,
              removeRuleAction: makeRemoveRuleAction(fields),
              valueEditor: CustomValueEditor,
              combinatorSelector: CombinatorSelector,
              operatorSelector: OperatorSelector,
              fieldSelector: FieldSelector,
            }}
            disabled={isDisabled}
          />
        </QueryBuilderAntD>

This is my Add Rule Button component:

export const AddRuleAction = (props: AntDActionProps) =>
  props.level > 1 ? null : (
    <Button size="lg" variant="success" onClick={(d) => props.handleOnClick(d)} disabled={props.disabled}>
      <PlusIcon className="mr-2" /> Rule
    </Button>
  )

This is Field Selection component:

const FieldSelector = (props: FieldSelectorProps) => {
    const { control, setValue, getValues } = useFormContext()
    const namePrefix = `${name}[${props.path[0]}]`

    const rules = getValues(name);
    const usedRules = new Set(rules?.map((rule: RuleType) => rule.field));

    filteredOptions = filterUsedRules(props.options as FullOption[], rules)
    
    return (
      <div className="flex">
        <FormField
          control={control}
          name={`${namePrefix}.field`}
          rules={{ required: true }}
          render={({ field }) => (
            <Select
              value={field.value}
              onValueChange={(d) => {
                props.handleOnChange(d)
                field.onChange(d)
                if (props.value !== d) {
                  setValue(`${namePrefix}.value`, [], { shouldDirty: true })
                }
              }}
              disabled={props.disabled}
            >
              <SelectTrigger className="h-10 min-w-[120px] w-fit">
                <SelectValue placeholder={props.value} data-testid="qb-field-selector" />
              </SelectTrigger>
              <SelectContent side="bottom">
                {props.options?.map((p: BaseOption, index: number) => (
                  <SelectItem key={index} value={`${p.value}`}>
                    {p.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          )}
        />
      </div>
    )
  }

I tried filtering out the used rules (taken from local state) and removing those from props.options but then the ones I have already used fields like A, B those do not show up in their rule field selector as they have filtered out!

So looks like when adding new Rule, it should be filtered there so those options do not appear for that Rule.

Any help would be appreciated.

1 Answer 1

0

The field selector below will disallow the selection of fields that are selected in sibling rules (that is, rules within the same group) unless that rule already has the field selected.

// src/FieldSelector.tsx

import type { FullField, RuleGroupType, FieldSelectorProps } from 'react-querybuilder';
import {
  ValueSelector,
  findPath,
  getParentPath,
  getQuerySelectorById,
  useQueryBuilderSelector,
  isRuleGroup,
} from 'react-querybuilder';

export const FieldSelector = (props: FieldSelectorProps) => {
  // Get the full query from RQB store
  const query = useQueryBuilderSelector(
    getQuerySelectorById(props.schema.qbId)
  );
  // On initial render `query` will be undefined, so just bail out.
  if (!query) return null;
  // Get the list of fields selected in sibling rules
  const siblingFields = (
    findPath(getParentPath(props.path), query) as RuleGroupType
  ).rules.map(r => (isRuleGroup(r) ? '' : r.field));
  // Define the list of acceptable fields for this rule. Filter out any
  // already-selected fields except if it matches this rule's field.
  const thisRuleFields = (props.options as FullField[]).filter(
    ({ name }) => name === props.value || !siblingFields.includes(name)
  );

  // Render the default value selector with the same props except
  // for the option list.
  return <ValueSelector {...props} options={thisRuleFields} />;
};

One thing that may help the user experience here is setting autoSelectField={false} on the <QueryBuilder /> props.

Here is a working sandbox using the component: https://codesandbox.io/p/devbox/6yhlvl?file=%2Fsrc%2FFieldSelector.tsx

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.