Undo & Redo
Enable the history prop on <Diagram> and every graph edit becomes reversible. The command manager records each mutation; useGraphHistory() exposes undo / redo, and useGraphHistoryStack() reports live undo/redo counts so a toolbar can disable its buttons and show how deep the stack is.
Undo & Redo is one of the models owned by the <Diagram> root, switched on with the history prop. The history hooks throw outside a <Diagram history>; a bare <GraphProvider> has no command manager.
The editor below records each change. Double-click the blank canvas to drop a card, drag cards around, then Undo / Redo to step through the history. Shuffle colors recolors every card without touching the stack, Clear board removes everything as a single undoable command, and Clear history discards the stack while leaving the board intact.
Enabling history
history turns on the command manager for the diagram. Pass true for the defaults, or an options object to configure it:
<Diagram initialCells={initialCells} history>
<Paper renderElement={renderElement} />
</Diagram>
Without history, every mutation is permanent the moment it happens. With it, each change is recorded onto the undo stack, ready to be reversed.
Keyboard shortcuts
With the diagram's built-in interactions on (the default) and history enabled, Ctrl/Cmd+Z undoes the last command and Shift+Ctrl/Cmd+Z redoes it. Switch just these off with interactions={{ commandManager: false }}; the command manager keeps recording, so the buttons and useGraphHistory still work.
For every interaction flag, see Built-in interactions. To bind your own shortcuts, see Custom Keyboard Shortcuts.
Undo and redo controls
useGraphHistory() returns the imperative handle: undo, redo, resetHistory, and the underlying commandManager. It never re-renders, so reach for it from any button inside the <Diagram>:
import { useGraphHistory } from '@joint/react-plus';
function UndoRedoButtons() {
const { undo, redo } = useGraphHistory();
return (
<>
<button onClick={undo}>Undo</button>
<button onClick={redo}>Redo</button>
</>
);
}
Reactive status and clearing
useGraphHistoryStack() subscribes to the stacks and re-renders whenever they change. Use it to disable the buttons at the ends of the history and to show how many steps are available:
import { useGraphHistory, useGraphHistoryStack } from '@joint/react-plus';
function HistoryControls() {
const { undo, redo, resetHistory } = useGraphHistory();
const { canUndo, canRedo, undoCount, redoCount } = useGraphHistoryStack();
return (
<>
<button disabled={!canUndo} onClick={undo}>Undo ({undoCount})</button>
<button disabled={!canRedo} onClick={redo}>Redo ({redoCount})</button>
<button onClick={resetHistory}>Clear history</button>
</>
);
}
There are two different "clears", and they are not the same:
- Clearing the board (removing the cells) is an edit, so it lands on the undo stack like any other.
graph.clear()wraps every removal in one batch, so the command manager records it as a single command and one Undo restores the whole board. - Clearing the history with
resetHistory()empties both stacks without touching the graph. The cards stay; the past edits are simply forgotten.
Filtering recorded commands
Pass filterCommand to keep specific changes off the stack. It runs before each command is recorded and returns false to skip it:
const filterCommand = ({ eventOptions }) => eventOptions.untracked !== true;
<Diagram history={{ filterCommand }}>
<Paper renderElement={renderElement} />
</Diagram>
The callback receives { eventName, eventArgs, eventOptions }, where eventOptions is the options object passed to the mutation. The demo's Shuffle colors action recolors each element with setCellData(id, updater, { untracked: true }); the metadata argument rides along on the change event, so filterCommand skips it and the recolor never becomes an undo step.
Configuration
The history object forwards to the underlying command manager:
<Diagram
history={{
stackLimit: 50,
options: { /* further dia.CommandManager options */ },
}}
>
<Paper renderElement={renderElement} />
</Diagram>
stackLimit caps how many commands the undo stack keeps; older commands fall off the bottom. options passes through any remaining dia.CommandManager option.
The command manager
useGraphHistory().commandManager is the live dia.CommandManager instance, for anything the hooks don't cover: group several edits into one command with graph.startBatch() / graph.stopBatch(), or listen to its stack event to drive autosave.
const { commandManager } = useGraphHistory();