Skip to content

biobright/react-querybuilder

Β 
Β 

Repository files navigation

react-querybuilder

All Contributors

npm workflow status codecov.io

Screenshot

Getting Started

npm install react-querybuilder --save

OR

yarn add react-querybuilder

Demo

See live demo

OR

To run the demo yourself, go through the following steps:

  1. Clone this repo
  2. yarn Install npm packages
  3. yarn start Run a local server
  4. http://localhost:8080/ Visit your localhost in your browser

Usage

import QueryBuilder from 'react-querybuilder';

const fields = [
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
  { name: 'age', label: 'Age' },
  { name: 'address', label: 'Address' },
  { name: 'phone', label: 'Phone' },
  { name: 'email', label: 'Email' },
  { name: 'twitter', label: 'Twitter' },
  { name: 'isDev', label: 'Is a Developer?', defaultValue: false }
];

const dom = <QueryBuilder fields={fields} onQueryChange={logQuery} />;

function logQuery(query) {
  console.log(query);
}

API

The default export of this library is the <QueryBuilder /> React component. Named exports include the <Rule /> component (for use in custom <RuleGroup /> implementations) and a utility function, formatQuery.

QueryBuilder

<QueryBuilder /> supports the following properties:

query (Optional)

{id?: string, combinator: string, rules: ({field: string, value: any, operator: string} | {rules: ...[], combinator: string})[]}

The initial query, in JSON form (follows the same format as the parameter passed to the onQueryChange callback). id is optional. See the demo source for examples.

fields (Required)

The array of fields that should be used. Each field should be an object with the following signature:

interface Field {
  id?: string; // The field identifier (if not provided, then `name` will be used)
  name: string; // REQUIRED - the field name
  label: string; // REQUIRED - the field label
  operators?: { name: string; label: string }[]; // Array of operators (if not provided, then `getOperators()` will be used)
  valueEditorType?: 'text' | 'select' | 'checkbox' | 'radio' | null; // Value editor type for this field (if not provided, then `getValueEditorType()` will be used)
  inputType?: string | null; // Input type for text box inputs, e.g. 'text', 'number', or 'date' (if not provided, then `getInputType()` will be used)
  values?: { name: string; label: string }[]; // Array of values, applicable when valueEditorType is 'select' or 'radio' (if not provided, then `getValues()` will be used)
  defaultValue?: any; // Default value for this field (if not provided, then `getDefaultValue()` will be used)
  placeholder?: string; // Value to be displayed in the placeholder of the text field
}

Field objects can also contain other data. Each field object will be passed to the appropriate OperatorSelector and ValueEditor components as fieldData (see the section on controlElements).

context (Optional)

any

A "bucket" for passing arbitrary props down to custom components. The context prop is passed to each and every component, so it's accessible anywhere in the QueryBuilder component tree.

operators (Optional)

{name: string, label: string}[]

The array of operators that should be used. The default operators include:

[
  { name: '=', label: '=' },
  { name: '!=', label: '!=' },
  { name: '<', label: '<' },
  { name: '>', label: '>' },
  { name: '<=', label: '<=' },
  { name: '>=', label: '>=' },
  { name: 'contains', label: 'contains' },
  { name: 'beginsWith', label: 'begins with' },
  { name: 'endsWith', label: 'ends with' },
  { name: 'doesNotContain', label: 'does not contain' },
  { name: 'doesNotBeginWith', label: 'does not begin with' },
  { name: 'doesNotEndWith', label: 'does not end with' },
  { name: 'null', label: 'is null' },
  { name: 'notNull', label: 'is not null' },
  { name: 'in', label: 'in' },
  { name: 'notIn', label: 'not in' }
];

combinators (Optional)

{name: string, label: string}[]

The array of combinators that should be used for RuleGroups. The default set includes:

[
  { name: 'and', label: 'AND' },
  { name: 'or', label: 'OR' }
];

controlElements (Optional)

interface Controls {
  addGroupAction?: React.ComponentType<ActionWithRulesProps>;
  addRuleAction?: React.ComponentType<ActionWithRulesProps>;
  combinatorSelector?: React.ComponentType<CombinatorSelectorProps>;
  fieldSelector?: React.ComponentType<FieldSelectorProps>;
  notToggle?: React.ComponentType<NotToggleProps>;
  operatorSelector?: React.ComponentType<OperatorSelectorProps>;
  removeGroupAction?: React.ComponentType<ActionWithRulesProps>;
  removeRuleAction?: React.ComponentType<ActionProps>;
  rule?: React.ComponentType<RuleProps>;
  ruleGroup?: React.ComponentType<RuleGroupProps>;
  valueEditor?: React.ComponentType<ValueEditorProps>;
}

This is a custom controls object that allows you to override the control elements used. The following control overrides are supported:

  • addGroupAction: By default a <button /> is used. The following props are passed:
interface ActionWithRulesProps {
  label: string; // translations.addGroup.label, e.g. "+Group"
  title: string; // translations.addGroup.title, e.g. "Add group"
  className: string; // CSS classNames to be applied
  handleOnClick: (e: React.MouseEvent) => void; // Callback function to invoke adding a <RuleGroup />
  rules: (RuleGroupType | RuleType)[]; // Provides the number of rules already present for this group
  level: number; // The level of the current group
  context: any; // Container for custom props that are passed to all components
}
  • removeGroupAction: By default a <button /> is used. The following props are passed:
interface ActionWithRulesProps {
  label: string; // translations.removeGroup.label, e.g. "x"
  title: string; // translations.removeGroup.title, e.g. "Remove group"
  className: string; // CSS classNames to be applied
  handleOnClick: (e: React.MouseEvent) => void; // Callback function to invoke adding a <RuleGroup />
  rules: (RuleGroupType | RuleType)[]; // Provides the number of rules already present for this group
  level: number; // The level of the current group
  context: any; // Container for custom props that are passed to all components
}
  • addRuleAction: By default a <button /> is used. The following props are passed:
interface ActionWithRulesProps {
  label: string; // translations.addGroup.label, e.g. "+Rule"
  title: string; // translations.addGroup.title, e.g. "Add rule"
  className: string; // CSS classNames to be applied
  handleOnClick: (e: React.MouseEvent) => void; // Callback function to invoke adding a <RuleGroup />
  rules: (RuleGroupType | RuleType)[]; // Provides the number of rules already present for this group
  level: number; // The level of the current group
  context: any; // Container for custom props that are passed to all components
}
  • removeRuleAction: By default a <button /> is used. The following props are passed:
interface ActionProps {
  label: string; // translations.removeRule.label, e.g. "x"
  title: string; // translations.removeRule.title, e.g. "Remove rule"
  className: string; // CSS classNames to be applied
  handleOnClick: (e: React.MouseEvent) => void; // Callback function to invoke adding a <RuleGroup />
  level: number; // The level of the current group
  context: any; // Container for custom props that are passed to all components
}
  • combinatorSelector: By default a <select /> is used. The following props are passed:
interface CombinatorSelectorProps {
  options: { name: string; label: string }[]; // Same as 'combinators' passed into QueryBuilder
  value: string; // Selected combinator from the existing query representation, if any
  className: string; // CSS classNames to be applied
  handleOnChange: (value: any) => void; // Callback function to update query representation
  rules: (RuleGroupType | RuleType)[]; // Provides the number of rules already present for this group
  level: number; // The level of the current group
  context: any; // Container for custom props that are passed to all components
}
  • fieldSelector: By default a <select /> is used. The following props are passed:
interface FieldSelectorProps {
  options: Field[]; // Same as 'fields' passed into QueryBuilder
  value: string; // Selected field from the existing query representation, if any
  title: string; // translations.fields.title, e.g. "Fields"
  operator: string; // Selected operator from the existing query representation, if any
  className: string; // CSS classNames to be applied
  handleOnChange: (value: any) => void; // Callback function to update query representation
  level: number; // The level the group this rule belongs to
  context: any; // Container for custom props that are passed to all components
}
  • operatorSelector: By default a <select /> is used. The following props are passed:
interface OperatorSelectorProps {
  field: string; // Field name corresponding to this rule
  fieldData: Field; // The entire object from the fields array for this field
  options: { name: string; label: string }[]; // Return value of getOperators(field)
  value: string; // Selected operator from the existing query representation, if any
  title: string; // translations.operators.title, e.g. "Operators"
  className: string; // CSS classNames to be applied
  handleOnChange: (value: any) => void; // Callback function to update query representation
  level: number; // The level the group this rule belongs to
  context: any; // Container for custom props that are passed to all components
}
  • valueEditor: By default an <input type="text" /> is used. The following props are passed:
interface ValueEditorProps {
  field: string; // Field name corresponding to this rule
  fieldData: Field; // The entire object from the fields array for this field
  operator: string; // Operator name corresponding to this rule
  value: string; // Value from the existing query representation, if any
  title: string; // translations.value.title, e.g. "Value"
  handleOnChange: (value: any) => void; // Callback function to update the query representation
  type: 'text' | 'select' | 'checkbox' | 'radio'; // Type of editor to be displayed
  inputType: string; // @type of <input> if `type` is "text"
  values: any[]; // List of available values for this rule
  level: number; // The level the group this rule belongs to
  className: string; // CSS classNames to be applied
  context: any; // Container for custom props that are passed to all components
}
  • notToggle: By default, <label><input type="checkbox" />Not</label> is used. The following props are passed:
interface NotToggleProps {
  checked: boolean; // Whether the input should be checked or not
  handleOnChange: (checked: boolean) => void; // Callback function to update the query representation
  title: string; // translations.notToggle.title, e.g. "Invert this group"
  level: number; // The level of the group
  className: string; // CSS classNames to be applied
  context: any; // Container for custom props that are passed to all components
}
  • ruleGroup: By default, <RuleGroup /> is used. The following props are passed:
interface RuleGroupProps {
  id: string; // Unique identifier for this rule group
  parentId: string; // Identifier of the parent group
  combinator: string; // Combinator for this group, e.g. "and" / "or"
  rules: (RuleType | RuleGroupType)[]; // List of rules and/or sub-groups for this group
  translations: Translations; // The full translations object
  schema: Schema; // See `Schema` documentation below
  not: boolean; // Whether or not to invert this group
  context: any; // Container for custom props that are passed to all components
}
  • rule: By default, <Rule /> is used. The following props are passed:
interface RuleProps {
  id: string; // Unique identifier for this rule
  parentId: string; // Identifier of the parent group
  field: string; // Field name for this rule
  operator: string; // Operator name for this rule
  value: any; // Value for this rule
  translations: Translations; // The full translations object
  schema: Schema; // See `Schema` documentation below
  context: any; // Container for custom props that are passed to all components
}

The Schema object passed in the rule and ruleGroup props has the following signature:

interface Schema {
  fields: Field[];
  classNames: Classnames;
  combinators: { name: string; label: string }[];
  controls: Controls;
  createRule(): RuleType;
  createRuleGroup(): RuleGroupType;
  getLevel(id: string): number;
  getOperators(field: string): Field[];
  getValueEditorType(field: string, operator: string): 'text' | 'select' | 'checkbox' | 'radio';
  getInputType(field: string, operator: string): string | null;
  getValues(field: string, operator: string): { name: string; label: string }[];
  isRuleGroup(ruleOrGroup: RuleType | RuleGroupType): ruleOrGroup is RuleGroupType;
  onGroupAdd(group: RuleGroupType, parentId: string): void;
  onGroupRemove(groupId: string, parentId: string): void;
  onPropChange(prop: string, value: any, ruleId: string): void;
  onRuleAdd(rule: RuleType, parentId: string): void;
  onRuleRemove(id: string, parentId: string): void;
  showCombinatorsBetweenRules: boolean;
  showNotToggle: boolean;
}

getOperators (Optional)

(field: string) => { name: string; label: string; }[] | null

This is a callback function invoked to get the list of allowed operators for the given field. If null is returned, the default operators are used.

getValueEditorType (Optional)

(field: string, operator: string) => 'text' | 'select' | 'checkbox' | 'radio' | null

This is a callback function invoked to get the type of ValueEditor for the given field and operator. Allowed values are "text" (the default if the function is not provided or if null is returned), "select", "checkbox", and "radio".

getInputType (Optional)

(field: string, operator: string) => string

This is a callback function invoked to get the type of <input /> for the given field and operator (only applicable when getValueEditorType returns "text" or a falsy value). If no function is provided, "text" is used as the default.

getValues (Optional)

(field: string, operator: string) => { name: string; label: string; }[]

This is a callback function invoked to get the list of allowed values for the given field and operator (only applicable when getValueEditorType returns "select" or "radio"). If no function is provided, an empty array is used as the default.

getDefaultField (Optional)

string | ((fieldsData: Field[]) => string)

The default field for new rules. This can be a string identifying the default field, or a function that returns a field name.

getDefaultValue (Optional)

(rule: Rule) => any

This function returns the default value for new rules.

onQueryChange (Optional)

(queryJSON: RuleGroup) => void

This is a notification that is invoked anytime the query configuration changes. The query is provided as a JSON structure, as shown below:

{
  "combinator": "and",
  "not": false,
  "rules": [
    {
      "field": "firstName",
      "operator": "null",
      "value": ""
    },
    {
      "field": "lastName",
      "operator": "null",
      "value": ""
    },
    {
      "combinator": "and",
      "rules": [
        {
          "field": "age",
          "operator": ">",
          "value": "30"
        }
      ]
    }
  ]
}

controlClassnames (Optional)

This can be used to assign specific CSS classes to various controls that are created by the <QueryBuilder />. This is an object with the following signature:

interface Classnames {
  queryBuilder?: string; // Root <div> element
  ruleGroup?: string; // <div> containing the RuleGroup
  header?: string; // <div> containing the RuleGroup header controls
  combinators?: string; // <select> control for combinators
  addRule?: string; // <button> to add a Rule
  addGroup?: string; // <button> to add a RuleGroup
  removeGroup?: string; // <button> to remove a RuleGroup
  notToggle?: string; // <label> on the "not" toggle
  rule?: string; // <div> containing the Rule
  fields?: string; // <select> control for fields
  operators?: string; // <select> control for operators
  value?: string; // <input> for the field value
  removeRule?: string; // <button> to remove a Rule
}

translations (Optional)

This can be used to override translatable texts applied to various controls that are created by the <QueryBuilder />. This is an object with the following properties:

{
  fields: {
    title: "Fields",
  },
  operators: {
    title: "Operators",
  },
  value: {
    title: "Value",
  },
  removeRule: {
    label: "x",
    title: "Remove rule",
  },
  removeGroup: {
    label: "x",
    title: "Remove group",
  },
  addRule: {
    label: "+Rule",
    title: "Add rule",
  },
  addGroup: {
    label: "+Group",
    title: "Add group",
  },
  combinators: {
    title: "Combinators",
  },
  notToggle: {
    title: "Invert this group",
  }
}

showCombinatorsBetweenRules (Optional)

boolean

Pass true to show the combinators (and/or) between rules and rule groups instead of at the top of rule groups. This can make some queries easier to understand as it encourages a more natural style of reading.

showNotToggle (Optional)

boolean

Pass true to show the "Not" toggle switch for each rule group.

resetOnFieldChange (Optional)

boolean

Pass false not to reset operator and value for field change.

resetOnOperatorChange (Optional)

boolean

Pass true to reset value on operator change.

enableMountQueryChange (Optional)

boolean

Pass false to disable the onQueryChange on mount of component which will set default value.

formatQuery

formatQuery formats a given query in either SQL, parameterized SQL, JSON, or JSON without IDs (which can be useful if you need to serialize the rules). Example:

import { formatQuery } from 'react-querybuilder';

const query = {
  id: 'g-b6SQ6WCcup8e37xhydwHE',
  rules: [
    {
      id: 'r-zITQOjVEWlsU1fncraSNn',
      field: 'firstName',
      value: 'Steve',
      operator: '='
    },
    {
      id: 'r-zVx7ARNak3TCZNFHkwMG2',
      field: 'lastName',
      value: 'Vai',
      operator: '='
    }
  ],
  combinator: 'and',
  not: false
};

console.log(formatQuery(query, 'sql')); // '(firstName = "Steve" and lastName = "Vai")'
console.log(formatQuery(query, 'parameterized')); // { sql: "(firstName = ? and lastName = ?)", params: ["Steve", "Vai"] }

An options object can be passed as the second argument instead of a format string in order to have more detailed control over the output. The options object takes the following form:

interface FormatQueryOptions {
  format?: 'sql' | 'json' | 'json_without_ids' | 'parameterized'; // same as passing a `format` string instead of an options object
  valueProcessor?: (field: string, operator: string, value: any) => string; // see below for an example
  quoteFieldNamesWith?: string; // e.g. "`" to quote field names with backticks (useful if your field names have spaces)
}

For example, if you need to control the way the value portion of the output is processed, you can specify a custom valueProcessor (only applicable for format: "sql").

const query = {
  id: 'g-J5GsbcFmZ6xOJCLPPKIfE',
  rules: [
    {
      id: 'r-KneYcwIPPHDGSogtKhG4g',
      field: 'instrument',
      value: ['Guitar', 'Vocals'],
      operator: 'in'
    },
    {
      id: 'r-wz6AkZbzSyDYbPk1AxgvO',
      field: 'lastName',
      value: 'Vai',
      operator: '='
    }
  ],
  combinator: 'and',
  not: false
};

const valueProcessor = (field, operator, value) => {
  if (operator === 'in') {
    // Assuming `value` is an array, such as from a multi-select
    return `(${value.map((v) => `"${v.trim()}"`).join(',')})`;
  } else {
    return `"${value}"`;
  }
};

console.log(formatQuery(query, { format: 'sql', valueProcessor })); // '(instrument in ("Guitar","Vocals") and lastName = "Vai")'

The 'json_without_ids' format will return the same query without the IDs. This can be useful, for example, if you need to save the query to the URL so that it becomes bookmarkable:

const query = {
  id: 'g-J5GsbcFmZ6xOJCLPPKIfE',
  rules: [
    {
      id: 'r-KneYcwIPPHDGSogtKhG4g',
      field: 'instrument',
      value: ['Guitar', 'Vocals'],
      operator: 'in'
    },
    {
      id: 'r-wz6AkZbzSyDYbPk1AxgvO',
      field: 'lastName',
      value: 'Vai',
      operator: '='
    }
  ],
  combinator: 'and',
  not: false
};

console.log(formatQuery(query, 'json_without_ids'));
// {
//   rules: [
//     {
//       field: 'instrument',
//       value: ['Guitar', 'Vocals'],
//       operator: 'in'
//     },
//     {
//       field: 'lastName',
//       value: 'Vai',
//       operator: '='
//     }
//   ],
//   combinator: 'and',
//   not: false
// };

Defaults

The following default configuration objects are exported for convenience.

  • defaultCombinators
  • defaultOperators
  • defaultTranslations
  • defaultValueProcessor

The following components are exported as well:

  • ActionElement - used for buttons ("Add rule", "Remove group", etc.)
  • NotToggle - used for the "Invert this group" toggle switch
  • ValueEditor - the default ValueEditor component
  • ValueSelector - used for drop-down lists: the combinator, field, and operator selectors

Development

Changelog Generation

We are using github-changes to generate the changelog.

To use it:

  1. tag your commit using semantic versioning
  2. run npm run generate-changelog
  3. enter your github credentials at the prompt
  4. commit
  5. push your commit and tags

Credits

This component was inspired by prior work from:

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Jake Boone

πŸ’» πŸ“– 🚧

Pavan Podila

πŸ’» πŸ“– ⚠️

Andrew Turgeon

πŸ’» ⚠️

AndrΓ© Drougge

πŸ’» ⚠️

Oumar Sharif DAMBABA

πŸ’»

Arte Ebrahimi

πŸ’» πŸ“– ⚠️

Carlos Azuaje

πŸ’»

Srinivas Damam

πŸ’»

Matthew Reishus

πŸ“–

Anish Duwal

πŸ’» ⚠️

RomanLamsal1337

πŸ’»

Dmitriy Kolesnikov

πŸ’»

Vitor Barbosa

πŸ’»

Laxminarayana

πŸ’» πŸ“–

Christian Mund

πŸ’» πŸ“–

Dallas Larsen

πŸ’»

Ayush Srivastava

πŸ“–

Fabio Espinosa

πŸ’» πŸ“– ⚠️

Anatoly Bubenkov

πŸ’» πŸ“– ⚠️

Saurabh Nemade

πŸ’» ⚠️

Edwin Xavier

πŸ’» πŸ“–

This project follows the all-contributors specification. Contributions of any kind welcome!

Packages

No packages published

Languages

  • TypeScript 96.0%
  • JavaScript 3.7%
  • SCSS 0.3%