Undo/redo is a signature feature of any editor — a drawing app, a form builder, a text field. The canonical model is three stacks: past (everything you can undo back to), present (the current value), and future (everything you've undone and could redo). Every action shuffles values between them. useUndoRedo packages that model into a hook with undo, redo, set, reset, and the canUndo/canRedo flags that drive your toolbar buttons.
Implement useUndoRedo(initialPresent). Return { state, set, undo, redo, reset, canUndo, canRedo, past, future }. A set branches (clears the redo future); undo/redo move the present between the stacks; both are no-ops at the ends.
function useUndoRedo(initialPresent) {
// returns { state, set, undo, redo, reset, canUndo, canRedo, past, future }
}
const { state, set, undo, redo, canUndo, canRedo } = useUndoRedo('');
set('hello'); set('hello world');
undo(); // state === 'hello'
redo(); // state === 'hello world'
set('a'); set('b'); undo(); // present 'a', future ['b']
set('c'); // branch: future cleared, canRedo === false
past (undoable history), present (current), future (redoable). state is present.set branches — push present to past, set the new value, and clear future (a fresh edit discards the redo path). No-op if unchanged.undo / redo — move present to future and pop past (undo), or the reverse (redo); no-ops when the relevant stack is empty.canUndo = past.length > 0, canRedo = future.length > 0. reset(next) clears both stacks. Keep the action callbacks stable.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.