Skip to main content
Version: Next

Arbitrary updates

Sometimes standard component actions don't meet specific requirements. React Query Builder provides tools to extend or replace the default behavior with custom implementations.

Multiple action elements

The default addRuleAction component always creates rules using the default configuration. However, you might need to provide multiple buttons that add rules with different pre-selected fields based on which button is clicked.

This example demonstrates a custom component that renders two buttons in place of the standard addRuleAction. Each button corresponds to a specific field. The click handler uses props.schema.getQuery() to retrieve the current query and props.schema.dispatchQuery() to update the query with the appropriate field selection.

import { useState } from 'react';
import {
  ActionElement,
  ActionProps,
  add,
  Field,
  QueryBuilder,
  RuleGroupType,
} from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const AddRulesAction = (props: ActionProps) => {
  const onClick = (field: string) => {
    props.schema.dispatchQuery(
      add(props.schema.getQuery(), { field, operator: '=', value: '' }, props.path)
    );
  };
  return (
    <>
      <ActionElement
        {...props}
        label="+ First Name Rule"
        handleOnClick={() => onClick('firstName')}
      />
      <ActionElement
        {...props}
        label="+ Last Name Rule"
        handleOnClick={() => onClick('lastName')}
      />
    </>
  );
};

const fields: Field[] = [
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
];

const initialQuery: RuleGroupType = {
  combinator: 'and',
  rules: [],
};

export default () => {
  const [query, setQuery] = useState(initialQuery);

  return (
    <div>
      <QueryBuilder
        fields={fields}
        query={query}
        onQueryChange={setQuery}
        controlElements={{ addRuleAction: AddRulesAction }}
      />
    </div>
  );
};

Custom query properties

While the standard query structure works well, it may not contain all the information you need. You might require additional properties in the query hierarchy and want to manage these properties through custom controls within the query builder. This example adds a caseSensitive property to each rule, using a checkbox alongside the default value editor to toggle this setting.

This example also demonstrates how the caseSensitive property integrates with a custom rule processor for formatQuery. When caseSensitive is false, the processor wraps the field with SQL's LOWER function and converts the value to lowercase before passing it to the default rule processor.

The example displays both the generated SQL query and the raw query object below the query builder interface.

Related: Adding and removing query properties

import { useState } from 'react';
import {
  defaultRuleProcessorSQL,
  Field,
  formatQuery,
  QueryBuilder,
  RuleGroupType,
  RuleProcessor,
  update,
  ValueEditor,
  ValueEditorProps,
} from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const CaseSensitivityValueEditor = (props: ValueEditorProps) => {
  const onChange = () => {
    props.schema.dispatchQuery(
      update(props.schema.getQuery(), 'caseSensitive', !props.rule.caseSensitive, props.path)
    );
  };
  return (
    <>
      <ValueEditor {...props} />
      <label>
        <input type="checkbox" checked={!!props.rule.caseSensitive} onChange={onChange} />
        {' Case sensitive'}
      </label>
    </>
  );
};

const ruleProcessor: RuleProcessor = (rule, opts) => {
  if (!rule.caseSensitive) {
    return defaultRuleProcessorSQL(
      { ...rule, field: `LOWER(${rule.field})`, value: rule.value.toLocaleLowerCase() },
      opts
    );
  }
  return defaultRuleProcessorSQL(rule, opts);
};

const fields: Field[] = [
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
];

const initialQuery: RuleGroupType = {
  combinator: 'or',
  rules: [
    { field: 'firstName', operator: '=', value: 'Adam', caseSensitive: true },
    { field: 'firstName', operator: '=', value: 'Eve', caseSensitive: false },
  ],
};

export default () => {
  const [query, setQuery] = useState(initialQuery);

  return (
    <div>
      <QueryBuilder
        fields={fields}
        query={query}
        onQueryChange={setQuery}
        controlElements={{ valueEditor: CaseSensitivityValueEditor }}
      />
      <pre>
        <code>{formatQuery(query, { format: 'sql', ruleProcessor })}</code>
      </pre>
      <pre>
        <code>{JSON.stringify(query, null, 2)}</code>
      </pre>
    </div>
  );
};