Getting Started

Undo and Redo

Set history on Builder to enable built-in undo and redo support for structural edits and value changes. The builder records inverse actions internally, so history stays smaller than full-query snapshots and still works with drag-and-drop, cloning, deletes, and inline edits.

History support
import '@vojtechportes/react-query-builder/styles.css';
import React, { useState } from 'react';
import {
Builder,
type DenormalizedQuery,
type IBuilderStateChange,
} from '@vojtechportes/react-query-builder';
export const MyBuilder = () => {
const [data, setData] = useState<DenormalizedQuery>(initialData);
const [historyState, setHistoryState] = useState({
canUndo: false,
canRedo: false,
});
const handleStateChange = (state: IBuilderStateChange) => {
setHistoryState({
canUndo: state.canUndo,
canRedo: state.canRedo,
});
};
return (
<>
<Builder
fields={fields}
data={data}
draggable
cloneable
history={{ maxEntries: 30, controls: true }}
onStateChange={handleStateChange}
onChange={setData}
/>
<p>Undo available: {String(historyState.canUndo)}</p>
<p>Redo available: {String(historyState.canRedo)}</p>
</>
);
};

How to enable it

  • history= enables history with default behavior.
  • history={{ maxEntries, controls }} enables history with custom configuration.
  • maxEntries limits how many undo steps are kept in memory.
  • controls controls whether the built-in Undo and Redo buttons are rendered.

What gets tracked

  • Adding, removing, cloning, and editing rules.
  • Adding, removing, cloning, and editing groups.
  • Drag-and-drop reordering and movement between groups.
  • Changes driven through the builder UI that emit through onChange.

State callbacks

  • onStateChange includes canUndo and canRedo so custom toolbars can stay in sync.
  • The built-in controls already use those flags and render disabled when no action is available.
  • Redo history is cleared after a new forward edit, which matches standard editor behavior.

Custom HistoryControls

Use components.HistoryControls when you want to change the placement or surrounding layout of the built-in history controls without reimplementing undo and redo behavior yourself.

HistoryControls override
import '@vojtechportes/react-query-builder/styles.css';
const components = {
HistoryControls: ({
undoButton,
redoButton,
canUndo,
canRedo,
}) => (
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<span>History</span>
{undoButton}
{redoButton}
<small>{canUndo ? 'Undo ready' : 'No undo yet'}</small>
<small>{canRedo ? 'Redo ready' : 'No redo yet'}</small>
</div>
),
};
<Builder
fields={fields}
data={data}
history
components={components}
onChange={setData}
/>;
// HistoryControls receives:
// undoButton: React.ReactNode
// redoButton: React.ReactNode
// canUndo: boolean
// canRedo: boolean
// onUndo: () => void
// onRedo: () => void
  • The override receives ready-to-render undoButton and redoButton nodes.
  • It also receives canUndo, canRedo, onUndo, and onRedo when you need custom wrappers or auxiliary UI.
  • This lets you reorder, wrap, or annotate the default buttons without having to rebuild their disabled-state logic.

Related recipes

© Vojtěch Václav Porteš 2026 - All library contents are available under the MIT license.
Loading privacy preferences...