Getting Started

Builder Ref

Use useBuilderRef() with the Builder ref to access internal node actions and history from custom toolbars, menus, keyboard shortcuts, or surrounding workflow logic.

Basic builderRef setup
import '@vojtechportes/react-query-builder/styles.css';
import React, { useState } from 'react';
import {
Builder,
useBuilderRef,
type DenormalizedQuery,
type IBuilderFieldProps,
} from '@vojtechportes/react-query-builder';
const fields: IBuilderFieldProps[] = [
{
field: 'STATUS',
label: 'Status',
type: 'LIST',
operators: ['EQUAL', 'NOT_EQUAL'],
value: [
{ value: 'ACTIVE', label: 'Active' },
{ value: 'ARCHIVED', label: 'Archived' },
],
},
];
const initialData: DenormalizedQuery = [
{
type: 'GROUP',
value: 'AND',
isNegated: false,
children: [
{
field: 'STATUS',
operator: 'EQUAL',
value: 'ACTIVE',
},
],
},
];
export const BuilderRefExample = () => {
const [data, setData] = useState<DenormalizedQuery>(initialData);
const builderRef = useBuilderRef();
return (
<>
<button
type="button"
onClick={() => {
const rootGroupId = builderRef.current
?.getNodes()
.find(node => 'type' in node)?.id;
if (!rootGroupId) {
return;
}
builderRef.current?.addRule(
{
field: 'STATUS',
operator: 'NOT_EQUAL',
value: 'ARCHIVED',
},
rootGroupId
);
}}
>
Add rule
</button>
<Builder ref={builderRef} fields={fields} data={data} onChange={setData} />
</>
);
};

When to use it

  • Trigger builder changes from controls rendered outside the builder.
  • Implement keyboard shortcuts for clone, delete, undo, or redo.
  • Insert preconfigured rules or groups from business-specific UI flows.
  • Inspect normalized nodes or history without reimplementing builder internals.

Read methods

Reading builder state
const builderRef = useBuilderRef();
const allNodes = builderRef.current?.getNodes();
const singleNode = builderRef.current?.getNodeById(nodeId);
const denormalizedData = builderRef.current?.getData();
const isCityInUse = builderRef.current?.isFieldInUse('CITY');
const cityOptionState = builderRef.current?.getFieldOptionState('CITY');
  • getNodeById(id) returns one normalized node by id.
  • getNodes() returns the current normalized node array.
  • getData() returns the denormalized public query shape.
  • isFieldInUse(field) tells you whether a field is currently present in the query.
  • getFieldOptionState(field) returns runtime options and the current option status for that field.

Mutation methods

Mutating nodes
const builderRef = useBuilderRef();
builderRef.current?.cloneNode(nodeId);
builderRef.current?.moveNode(nodeId, 0, targetGroupId);
builderRef.current?.setNodeLock(nodeId, 'self');
builderRef.current?.unlockNode(nodeId);
builderRef.current?.replaceNode(nodeId, nextNode);
builderRef.current?.updateNode(nodeId, node => ({
...node,
readOnly: true,
}));
  • cloneNode, deleteNode, and moveNode follow the same behavior as the built-in UI controls.
  • addNode, addGroup, addRule, and insertNodes let you build structure imperatively.
  • replaceNode swaps a node directly, while updateNode is useful when you want the next value to depend on the current node.
  • setNodeLock, lockNode, and unlockNode write the same read-only states as the lock UI.

Dynamic field options

Managing field options
builderRef.current?.isFieldInUse('CITY');
builderRef.current?.getFieldOptionState('CITY');
builderRef.current?.setFieldOptionsStatus('CITY', 'loading');
builderRef.current?.setFieldOptions('CITY', [
{ value: 'PRG', label: 'Prague' },
{ value: 'BRN', label: 'Brno' },
]);
builderRef.current?.invalidateFieldOptions('CITY');
builderRef.current?.reloadFieldOptions('CITY');
builderRef.current?.clearFieldOptions('CITY');
  • setFieldOptionsStatus(field, status) lets surrounding app code reflect loading, success, idle, or error state.
  • setFieldOptions(field, options) replaces the runtime option set without changing the original fields array.
  • invalidateFieldOptions(field) drops runtime options and falls back to the static options defined in field.value.
  • clearFieldOptions(field) removes the runtime state entirely, which is useful when a dependent field leaves scope.

History methods

History access
const builderRef = useBuilderRef();
const history = builderRef.current?.getHistory();
builderRef.current?.undo();
builderRef.current?.redo();
builderRef.current?.setHistory({
past: [],
future: [],
});
  • undo() and redo() use the same history engine as the built-in controls.
  • getHistory() returns the current past and future stacks.
  • setHistory() lets you replace or clear the internal history state.
Data shapes
Most imperative methods use normalized nodes because that matches the internal builder engine. Use getData() when you need the public denormalized query shape.
© Vojtěch Václav Porteš 2026 - All library contents are available under the MIT license.
Loading privacy preferences...