Migrating to v7 or v8
React Query Builder version 8 is fully compatible with version 7 except for @react-querybuilder/chakra
(see Compatibility packages). If you are not using @react-querybuilder/chakra
, you can migrate from v7 to v8 with no code changes.More information
@react-querybuilder/chakra@7
supports Chakra UI version 2.@react-querybuilder/chakra@8
supports Chakra UI version 3.@react-querybuilder/chakra2
is a fork of @react-querybuilder/chakra@7.7.1
that will continue to support Chakra UI version 2 going forward.@react-querybuilder/chakra
, which led to the major version bump of all packages since we maintain consistent version numbers across all packages.
Version 7 shouldn't require many—if any—code changes when migrating from v6, although some of the defaults have changed. Also, taking advantage of the performance improvements, new features, and other conveniences may require some minor refactoring. A summary of the important changes is below.
Previous migration instructions: v5 to v6 / v4 to v5 / v3 to v4.
Breaking changes
React 18
- The minimum React version is now 18. (This is due to the new
react-redux
v9 dependency, but we're investigating ways to support React 16.8 and 17.)
TypeScript updates
- The minimum TypeScript version is now 5.1.
- The component props themselves haven't changed much from version 6, but their TypeScript interfaces have been overhauled.
- If you haven't specified generics on the prop interfaces for your custom subcomponents, you may not need to make any TypeScript-related changes.
QueryBuilder
props
-
The query type (extending
RuleGroupType
orRuleGroupTypeIC
) will be automatically inferred from thequery
ordefaultQuery
prop instead of relying on the now-deprecated (and ignored)independentCombinators
prop. See independent combinators. -
QueryBuilderProps
now requires four generic arguments.- This shouldn't affect JSX which renders a
<QueryBuilder />
component since the generic types can almost always be inferred from the props. - While all props are technically still optional, TypeScript may have problems inferring the generics if
fields
andquery
/defaultQuery
are not provided. - The four generic arguments of
QueryBuilderProps
represent, respectively, the query type (extendingRuleGroupType
orRuleGroupTypeIC
), the field type, the operator type, and the combinator type. The latter three must extendFullOption
or the more specific and expressiveFullField
/FullOperator
/FullCombinator
.
// Valid in version 6:
const qbp6: QueryBuilderProps = {};
// Version 7 with the defaults (equivalent to the "version 6" line above):
const qbp7: QueryBuilderProps<RuleGroupType, FullField, FullOperator, FullCombinator> = {};
// Also valid in version 7 (since FullField, FullOperator, and FullCombinator all extend FullOption):
const qbp7a: QueryBuilderProps<RuleGroupType, FullOption, FullOption, FullOption> = {}; - This shouldn't affect JSX which renders a
Option
-type props
- Custom subcomponents must now accept any option-type props (fields, operators, values, etc.) as an extension of
FullOption
instead ofOption
.Full*
types are identical to their version 6 counterparts except for thevalue
property, which is required and must be the same type as thename
property.*ByValue
types are identical to theirFull*
counterparts except that thename
property is optional.- Relevant interfaces include the following:
Interface "Full*" counterpart "*ByValue" counterpart Field
FullField
FieldByValue
Operator
FullOperator
OperatorByValue
Combinator
FullCombinator
CombinatorByValue
- The first generic argument of
ValueEditorProps
,ValueSelectorProps
, andFieldSelectorProps
must extendFullOption
instead ofOption
as in version 6.- In the case of
ValueEditorProps
andFieldSelectorProps
, preferFullField
overFullOption
. - Where editor/selector prop type generics have been used, upgrading to version 7 should only require a minor update similar to this:
type MyFieldNames = "firstName" | "lastName" | "birthdate";
-const MyValueEditor = (props: ValueEditorProps<Field<MyFieldNames>>) => {
+const MyValueEditor = (props: ValueEditorProps<FullField<MyFieldNames>>) => {
// ^ Field -> FullField
return <ValueEditor {...props} />
}
- In the case of
Parser functions removed from main bundle
Since the parser functions are used less frequently than other utility functions—and not generally alongside each other—they have been removed from the main export. Although these functions have been available as separate exports since version 6 (along with formatQuery
and transformQuery
), they could still be imported from "react-querybuilder"
. They are now available only as separate exports. (This change reduced the main bundle size by almost 50%.)
// Version 6 only
-import { parseCEL } from "react-querybuilder"
-import { parseJsonLogic } from "react-querybuilder"
-import { parseMongoDB } from "react-querybuilder"
-import { parseSQL } from "react-querybuilder"
// Version 6 or 7
+import { parseCEL } from "react-querybuilder/parseCEL"
+import { parseJsonLogic } from "react-querybuilder/parseJsonLogic"
+import { parseMongoDB } from "react-querybuilder/parseMongoDB"
+import { parseSQL } from "react-querybuilder/parseSQL"
// (New in version 7)
+import { parseSpEL } from "react-querybuilder/parseSpEL"
+import { parseJSONata } from "react-querybuilder/parseJSONata"
Miscellaneous
@react-querybuilder/material
uses the MUITextField
component instead ofInput
in versions greater than 7.7.0. See Preload MUI components.@react-querybuilder/mantine
now requires Mantine 7 or greater.- The
"json_without_ids"
export format now explicitly removes theid
andpath
properties from the output without removing other properties. Previously this format would only include specific properties, removing any custom properties. The following command will replicate the previous behavior:JSON.stringify(query, ['rules', 'field', 'value', 'operator', 'combinator', 'not', 'valueSource']);
parseMongoDB
now generates more concise queries when it encounters$not
operators that specify a single, boolean condition. Whereas previously that would yield a rule group withnot: true
, it now generates a rule with a negated operator ("="
becomes"!="
,"contains"
becomes"doesNotContain"
, etc.). To prevent this behavior, set thepreventOperatorNegation
option totrue
. (This change does not apply to operators defined in theadditionalOperators
option.)- Paths are now declared with a new type alias
Path
instead ofnumber[]
. The actual type is the same:type Path = number[]
. - The
RuleGroupTypeIC
interface now includescombinator?: undefined
to ensure that query objects implementing independent combinators do not containcombinator
properties. - The
useQueryBuilder
hook must now be called from a descendant component ofQueryBuilderStateProvider
. For example usage, see theQueryBuilder
source code. - The
useStopEventPropagation
hook, called from the defaultRule
andRuleGroup
components, now takes a single function as its parameter instead of an object map of functions. It must be run for each wrapped function individually.
New features
Performance improvements
Each prop passed to QueryBuilder
should have a stable reference or be memoized.
Props, components, and derived values are aggressively memoized in version 7 with React.memo
, useMemo
, and useCallback
. This can noticeably improve rendering performance for large queries, especially when using certain style libraries. To take advantage of this change, every prop (except query
, if used) must have a stable reference or at least be memoized. For related reasons, we encourage using QueryBuilder
as an uncontrolled component by specifying defaultQuery
instead of query
.
You can avoid unstable references by moving unchanging props, including object, array, and function definitions, outside the component rendering function. This commonly includes the fields
array and onQueryChange
callback. For props that must be defined inside the component, memoize them with useMemo
or useCallback
. Particularly avoid defining props inline in the JSX.
/**
* BAD:
*/
function App() {
const { t } = useTranslation(); // (<-- third-party i18n library)
const [query, setQuery] = useState();
// This function gets recreated on each render
const getOperators = (field: Field) => t(defaultOperators);
return (
<QueryBuilder
// Avoid inline function definitions
onQueryChange={q => setQuery(q)}
// Avoid inline array definitions
fields={[
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
]}
// See above
getOperators={getOperators}
/>
);
}
/**
* GOOD:
*/
// Fields array never changes, so it can be defined outside the component
const fields: Field[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
];
function App() {
const { t } = useTranslation(); // (<-- third-party i18n library)
const [query, setQuery] = useState();
// Memoize functions with useCallback. Since `t` (probably) has a
// stable reference, this function will rarely be recreated, if ever
const getOperators = useCallback((field: Field) => t(defaultOperators), [t]);
return (
<QueryBuilder
// React useState/useReducer setters always have stable references,
// even when defined within the render method
onQueryChange={setQuery}
// See above
fields={fields}
// See above
getOperators={getOperators}
/>
);
}
If you're using a state setter function generated by useState
or useReducer
, you don't need to memoize it. Relatedly, TypeScript no longer throws an error in version 7 if you pass the setter function directly (onQueryChange={setQuery}
), as long as you're not specifying generics on the query type. Version 6 and earlier required the onQueryChange
prop to be explicitly typed as (q: RuleGroupType) => void
.
Option list value
identifiers
In all prior versions of React Query Builder, props and properties that accepted an OptionList
array or extension thereof (such as fields
, combinators
, etc.) required a name
property as the unique identifier for each member within the list. In version 7, list members may use a value
property as their unique identifier instead of name
. If both name
and value
are present for a given item, value
will take precedence. This should make it easier to integrate libraries like react-select
that expect options to extend { value: string; label: string; }
.
QueryBuilder
will ensure that all option list members have both name
and value
properties, and will make them equivalent if only one is provided. Therefore, all subcomponents can assume both properties will be present in their option list props.
The Field
, Combinator
, and Operator
interfaces, which extend Option
, still require a name
property when used directly. You can use the FlexibleOption
type instead to avoid the name
property requirement, but you will lose intellisense for the additional properties of the more specific types.
Field identifier types (name
/value
properties) will now be inferred from the fields
prop if they have been narrowed from string
. These narrowed types will be applied to subcomponents and other props that take fields or field identifiers as arguments/props. For example, if the fields
prop is type Field<MyFields>[]
, then the fieldSelector
property of the controlElements
prop will be inferred as ComponentType<FieldSelectorProps<FullField<MyFields>>>
instead of the default ComponentType<FieldSelectorProps<FullField<string>>>
. This allows subcomponents themselves to be defined with narrowed identifier types without TypeScript complaining about string
not being assignable to the narrowed type.
Below is an example of a custom value editor with a narrowed field identifier type:
type MyFields = 'firstName' | 'lastName' | 'birthDate';
const fields: Field<MyFields>[] = [
{ name: 'firstName', label: 'First Name' },
{ name: 'lastName', label: 'Last Name' },
{ name: 'birthDate', label: 'Birth Date' },
];
function MyValueEditor(props: ValueEditorProps<FullField<MyFields>>) {
if (props.field === 'invalid') {
// ^ TypeScript error: `MyFields` and `invalid` have no overlap
return null;
}
return <ValueEditor {...props} />;
}
function App() {
return (
<QueryBuilder
// Narrowed field identifier type inferred here:
fields={fields}
// No TypeScript error here, even though MyValueEditor
// has a narrowed field identifier type.
controlElements={{ valueEditor: MyValueEditor }}
/>
);
}
Bulk override action elements, value selectors
Two "bulk override" properties have been added to the controlElements
prop: actionElement
and valueSelector
. When actionElement
is defined, it will be used for every component that defaults to ActionElement
. The same is true for valueSelector
and components that default to ValueSelector
, including ValueEditor
in cases where it renders a value selector. This makes it possible to define custom components for all buttons and all selectors at once instead of one-by-one. Assignments to the more specific controlElements
properties will take precedence over the bulk overrides.
controlElements property | Sets default for |
---|---|
valueSelector | combinatorSelector , fieldSelector , operatorSelector , valueSourceSelector , valueEditor (when rendering a value selector) |
actionElement | addGroupAction , addRuleAction , cloneGroupAction , cloneRuleAction , lockGroupAction , lockRuleAction , removeGroupAction , removeRuleAction |
These same two properties have also been added to the controlClassnames
prop, but they add to, not replace, classnames declared for specific components.
Query selector, getter, and dispatcher
Passing the query object to subcomponents using the context
prop is no longer necessary or recommended.
Three new methods are available that should make it easier to manage arbitrary query updates from custom components. The first two methods are available on the schema
prop which is passed to every component, and should only be used in event handlers. The latter two methods are React Hooks and should follow the appropriate rules.
Method | Description |
---|---|
props.schema.getQuery() | Returns the current root query object. Use only in event handlers. |
props.schema.dispatchQuery(query) | Updates the internal query state and calls the onQueryChange callback. Use only in event handlers. |
useQueryBuilderQuery() | React Hook that returns the current root query object. |
useQueryBuilderSelector(selector) | Redux selector Hook for the internal store. |
Notes:
- Prefer
useQueryBuilderQuery
overuseQueryBuilderSelector
. If necessary, a selector foruseQueryBuilderSelector
that retrieves the current root query object can be generated withgetQuerySelectorById(props.schema.qbId)
. - These functions all use a custom Redux context behind the scenes, hence the "selector" nomenclature.
- To register the React Query Builder Redux store with Redux DevTools, change all
"react-querybuilder"
imports to"react-querybuilder/debug"
.
- To register the React Query Builder Redux store with Redux DevTools, change all
- Previously, updates that couldn't be performed with
handleOnChange
orhandleOnClick
event handlers had to use external state management in conjunction with theadd
/update
/remove
utilities. To support this, we recommended including the query object as a property of thecontext
prop. That workaround is no longer necessary or recommended.
insert
utility method
The new insert
utility method can be used to insert a new rule or group anywhere in the query hierarchy. insert
is similar to add
except that the new rule or group can be inserted at any path (add
can only append a rule or group to the end of a group's rules
array). The option replace: true
will replace the rule or group at the specified path.
Standalone layout stylesheet
Default layout styles (flex direction, alignment, spacing, etc.) are now available as a standalone stylesheet query-builder-layout.css
/.scss
. The default stylesheet, query-builder.css
/.scss
, contains the layout styles along with the more decorative styles like colors and border styles. The effective styles—both layout and decorative—of the default stylesheet have not changed from version 6.
Field data passed to get*
callbacks
Most of the get*
callback props now receive an additional "meta" parameter with a fieldData
property (more properties may be added to the "meta" object in the future). fieldData
is the full Field
object from the fields
array for the given field
name, including any custom properties like datatype
. This eliminates the need to search for the field object based solely on the field's name
. Instead of something like fields.find(f => f.name === field)
, you can retrieve fieldData
from the last parameter.
The following callback props provide the new "meta" parameter: getDefaultOperator
, getDefaultValue
, getInputType
, getOperators
, getRuleClassname
, getValueEditorSeparator
, getValueEditorType
, getValues
, and getValueSources
.
Field data passed to formatQuery
rule processors
Custom rule processors for formatQuery
now receive the full Field
object in the options parameter, as long as a fields
array is provided alongside ruleProcessor
. In TypeScript, the member type of the fields
array now requires a label: string
property (just like QueryBuilder
's fields
prop). Previously, only name
was required.
Simpler PostgreSQL compatibility for formatQuery
formatQuery
can natively generate PostgreSQL-compatible parameterized SQL using the new numberedParams
option in conjunction with paramPrefix: "$"
. Previously, PostgreSQL compatibility required manually post-processing the generated SQL to replace the "?" placeholders with a sequential series of numbers preceded by "$".
Shift actions
A new showShiftActions
prop provides first class support for rearranging rules within a query without enabling drag-and-drop. When showShiftActions
is true
, two buttons will appear at the front of each rule and group (except the root group), stacked vertically by default. The first/upper button will shift the rule or group one spot higher, while the second/lower button will shift it one spot lower. Pressing the modifier key (Alt
on Windows/Linux, Option
/⌥
on Mac) while clicking will clone the rule/group instead of just moving it.
Related additions:
ShiftActions
component (with a corresponding component in each compatibility package) that renders "shift up" and "shift down" action buttons. The default styles remove the border and background from these buttons, leaving only theshiftActionUp.label
/shiftActionDown.label
translation properties visible.useShiftActions
hook, called by theShiftActions
component, returnsshiftUp
/shiftDown
methods and determines whether either button should be disabled. (Within the root group, "shift up" is disabled for the very first rule or group and "shift down" is disabled for the very last rule or group).- New properties on the
translations
prop:shiftActionUp
andshiftActionDown
.
Accessibility
Accessibility is improved with the addition of a title
attribute to the outermost <div>
of each rule group. The text of this attribute can be customized with the accessibleDescriptionGenerator
function prop. The default implementation is exported as generateAccessibleDescription.
Drag-and-drop canDrop
callback
<QueryBuilderDnD />
and <QueryBuilderDndWithoutProvider />
from @react-querybuilder/dnd
now accept a canDrop
callback prop. If provided, the function will be called when dragging a rule or group. The only parameter will be an object containing dragging
and hovering
properties, representing the rule/group being dragged and the rule/group over which it is hovered, respectively. The objects will also contain the path
of each item. If canDrop
returns false
, dropping the item at its current position will have no effect on the query. Otherwise the normal drag-and-drop rules will apply.
Enhanced parseNumber
behavior
The parseNumber
function now delegates parsing to numeric-quantity
, which is essentially an enhanced version of parseFloat
. The default behavior has not changed, but a new "enhanced" option will ignore trailing invalid characters (e.g., the "abc" in "123abc"). This matches the behavior of the "native" option, which uses parseFloat
directly, except it returns the original string when parsing fails instead of NaN
.
SpEL parser
The new parseSpEL
function converts SpEL expressions to React Query Builder query objects.
Updated default labels
Main package
Key | Old | New | Notes |
---|---|---|---|
translations.addRule.label | "+Rule" | "+ Rule" | Space added between "+" and "Rule" |
translations.addGroup.label | "+Group" | "+ Group" | Space added between "+" and "Group" |
translations.removeRule.label | "x" | "⨯" | HTML entity ✗ / ⨯ |
translations.removeGroup.label | "x" | "⨯" | HTML entity ✗ / ⨯ |
Compatibility packages
Several compatibility packages take advantage of the new ability to use a ReactNode
as a label
(previously only string
s were allowed). These packages override the default label
for non-text components (lock*
, clone*
, remove*
, and dragHandle
) with SVG icons from their official icon packages. This brings them more in line with their respective design systems by default. Set translations={defaultTranslations}
to avoid the SVG icons.
Ant Design (@react-querybuilder/antd
)
Icon package: @ant-design/icons
Key | Icon |
---|---|
translations.removeGroup.label | <CloseOutlined /> |
translations.removeRule.label | <CloseOutlined /> |
translations.cloneRule.label | <CopyOutlined /> |
translations.cloneRuleGroup.label | <CopyOutlined /> |
translations.lockGroup.label | <UnlockOutlined /> |
translations.lockRule.label | <UnlockOutlined /> |
translations.lockGroupDisabled.label | <LockOutlined /> |
translations.lockRuleDisabled.label | <LockOutlined /> |
translations.shiftActionUp.label | <UpOutlined /> |
translations.shiftActionDown.label | <DownOutlined /> |
Bootstrap (@react-querybuilder/bootstrap
)
Icon package: bootstrap-icons
Note: The
BootstrapDragHandle
component has been removed. It is unnecessary now that the default drag handle component can accept aReactNode
as itslabel
.
Key | Icon |
---|---|
translations.removeGroup.label | <i className="bi bi-x" /> |
translations.removeRule.label | <i className="bi bi-x" /> |
translations.cloneRule.label | <i className="bi bi-copy" /> |
translations.cloneRuleGroup.label | <i className="bi bi-copy" /> |
translations.dragHandle.label | <i className="bi bi-grip-vertical" /> |
translations.lockGroup.label | <i className="bi bi-unlock" /> |
translations.lockRule.label | <i className="bi bi-unlock" /> |
translations.lockGroupDisabled.label | <i className="bi bi-lock" /> |
translations.lockRuleDisabled.label | <i className="bi bi-lock" /> |
translations.shiftActionUp.label | <i className="bi bi-chevron-compact-up" /> |
translations.shiftActionDown.label | <i className="bi bi-chevron-compact-down" /> |
Chakra UI (@react-querybuilder/chakra
)
Chakra UI published its own icon package for version 2 (@chakra-ui/icons
), but recommends react-icons
for version 3. @react-querybuilder/chakra
version 8 has been updated accordingly, using icons from react-icons/fa
by default.
Key | Chakra UI v2 Icon ( @react-querybuilder/chakra@7 ,@react-querybuilder/chakra2@* ) | Chakra UI v3 Icon ( @react-querybuilder/chakra@8 ) |
---|---|---|
translations.dragHandle.label | <DragHandleIcon /> (via ChakraDragHandle , not translations ) | <FaGripVertical /> |
translations.removeGroup.label | <CloseIcon /> | <FaTimes /> |
translations.removeRule.label | <CloseIcon /> | <FaTimes /> |
translations.cloneRuleGroup.label | <CopyIcon /> | <FaCopy /> |
translations.cloneRule.label | <CopyIcon /> | <FaCopy /> |
translations.lockGroup.label | <UnlockIcon /> | <FaLockOpen /> |
translations.lockRule.label | <UnlockIcon /> | <FaLockOpen /> |
translations.lockGroupDisabled.label | <LockIcon /> | <FaLock /> |
translations.lockRuleDisabled.label | <LockIcon /> | <FaLock /> |
translations.shiftActionDown.label | <ChevronDownIcon /> | <FaChevronDown /> |
translations.shiftActionUp.label | <ChevronUpIcon /> | <FaChevronUp /> |
Fluent UI (@react-querybuilder/fluent
)
Icon package: @fluentui/react-icons-mdl2
Key | Icon |
---|---|
translations.removeGroup.label | <CancelIcon /> |
translations.removeRule.label | <CancelIcon /> |
translations.cloneRule.label | <DuplicateRowIcon /> |
translations.cloneRuleGroup.label | <DuplicateRowIcon /> |
translations.dragHandle.label | <GripperDotsVerticalIcon /> |
translations.lockGroup.label | <UnlockIcon /> |
translations.lockRule.label | <UnlockIcon /> |
translations.lockGroupDisabled.label | <LockIcon /> |
translations.lockRuleDisabled.label | <LockIcon /> |
translations.shiftActionDown.label | <ChevronDownIcon /> |
translations.shiftActionUp.label | <ChevronUpIcon /> |
MUI/Material (@react-querybuilder/material
)
Icon package: @mui/icons-material
Key | Icon |
---|---|
translations.removeGroup.label | <Close /> |
translations.removeRule.label | <Close /> |
translations.cloneRule.label | <ContentCopy /> |
translations.cloneRuleGroup.label | <ContentCopy /> |
translations.lockGroup.label | <LockOpen /> |
translations.lockRule.label | <LockOpen /> |
translations.lockGroupDisabled.label | <Lock /> |
translations.lockRuleDisabled.label | <Lock /> |
translations.shiftActionDown.label | <ShiftDown /> |
translations.shiftActionUp.label | <ShiftUp /> |