Skip to main content
Version: Next

Managing operators

A common requirement of React Query Builder implementations is to limit or alter the operators based on the selected field. For example, when a field represents a date value, a label of "before" might be more appropriate for the < operator than the default "<". A number-type field might use a label of "less than" for the < operator, while a text field probably wouldn't use the < operator at all.

Field operators property

The first way to address this requirement is with the field operators property. The operators property of a field determines which operators are displayed once the user selects that field in the field selector. The downside of this method is that the operators list must be fully defined for each field, potentially duplicating lists across fields with equivalent data types.

import { defaultOperators, Field, QueryBuilder, RuleGroupType } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const fields: Field[] = [
  {
    name: 'name',
    label: 'Name',
    operators: [
      { name: '=', label: 'is' },
      ...defaultOperators.filter(op => ['contains', 'beginsWith', 'endsWith'].includes(op.name)),
    ],
  },
  {
    name: 'birthday',
    label: 'Birthday',
    inputType: 'date',
    operators: [
      { name: '=', label: 'on' },
      { name: '<', label: 'before' },
      { name: '>', label: 'after' },
    ],
  },
  {
    name: 'guitars',
    label: 'Guitars',
    inputType: 'number',
    operators: [
      { name: '=', label: 'equals' },
      { name: '<', label: 'less than' },
      { name: '>', label: 'greater than' },
    ],
  },
  {
    name: 'favoriteMovie',
    label: 'Favorite Movie',
    operators: [{ name: '=', label: 'is' }],
  },
];

const defaultQuery: RuleGroupType = {
  combinator: 'and',
  rules: [
    { field: 'name', operator: 'beginsWith', value: 'Stev' },
    { field: 'birthday', operator: '<', value: '1970-01-01' },
    { field: 'guitars', operator: '>', value: 5 },
    { field: 'favoriteMovie', operator: '=', value: 'Crossroads (1986)' },
  ],
};

export default () => <QueryBuilder fields={fields} defaultQuery={defaultQuery} />;

getOperators prop

The field operators property is useful when individual fields deviate from the default operator list, but the getOperators function prop allows you to put all operator lists and logic in one place.

getOperators is passed two arguments: the field identifier and a "meta" object with a fieldData property containing the full field definition. Based on that, you can gather information from the same array provided to the fields prop (or any other data source) and return a list of operator names and labels.

The example below takes advantage of the ability to add arbitrary properties to field definitions. A non-standard datatype property on each field is used to determine which operators are displayed and how they are labeled. The defaultOperators export is utilized where appropriate. It also shows how, if present, a field's operators property will take precedence over the result of the getOperators function (see the fourth rule, where the field selection of "Favorite Movie" limits the operator list to "is" even though its datatype is "text").

import { defaultOperators, Field, QueryBuilder, RuleGroupType } from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.css';

const fields: Field[] = [
  { name: 'name', label: 'Name', datatype: 'text' },
  { name: 'birthday', label: 'Birthday', datatype: 'date', inputType: 'date' },
  { name: 'guitars', label: 'Guitars', datatype: 'number', inputType: 'number' },
  {
    name: 'favoriteMovie',
    label: 'Favorite Movie',
    datatype: 'text',
    operators: [{ name: '=', label: 'is' }],
  },
];

const getOperators = (fieldName: string, { fieldData }: { fieldData: Field }) => {
  switch (fieldData.datatype) {
    case 'text':
      return [
        { name: '=', label: 'is' },
        { name: '!=', label: 'is not' },
        ...defaultOperators.filter(op =>
          [
            'contains',
            'beginsWith',
            'endsWith',
            'doesNotContain',
            'doesNotBeginWith',
            'doesNotEndWith',
            'null',
            'notNull',
            'in',
            'notIn',
          ].includes(op.name)
        ),
      ];
    case 'number':
      return [
        ...defaultOperators.filter(op => ['=', '!='].includes(op.name)),
        { name: '<', label: 'less than' },
        { name: '<=', label: 'less than or equal to' },
        { name: '>', label: 'greater than' },
        { name: '>=', label: 'greater than or equal to' },
        ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),
      ];
    case 'date':
      return [
        { name: '=', label: 'on' },
        { name: '!=', label: 'not on' },
        { name: '<', label: 'before' },
        { name: '<=', label: 'on or before' },
        { name: '>', label: 'after' },
        { name: '>=', label: 'on or after' },
        ...defaultOperators.filter(op => ['null', 'notNull'].includes(op.name)),
      ];
  }
  return defaultOperators;
};

const defaultQuery: RuleGroupType = {
  combinator: 'and',
  rules: [
    { field: 'name', operator: 'beginsWith', value: 'Stev' },
    { field: 'birthday', operator: '<', value: '1970-01-01' },
    { field: 'guitars', operator: '>', value: 5 },
    { field: 'favoriteMovie', operator: '=', value: 'Crossroads (1986)' },
  ],
};

export default () => (
  <QueryBuilder fields={fields} defaultQuery={defaultQuery} getOperators={getOperators} />
);