useStateWithHistoryLoading saved progress…

useStateWithHistory

Undo/redo is a beloved feature and a fiddly one. The core is a useState that doesn't forget: every value it's held is recorded, and you can walk back and forward through that timeline. useStateWithHistory is that primitive — it behaves like useState, but also tracks a history array and a pointer into it, with back, forward, and go to move around.

Implement useStateWithHistory(initialValue, { capacity = 10 }). Return [value, set, controls] where controls has history, pointer, back(steps), forward(steps), and go(index). Setting a new value after going back truncates the forward history (a new branch, like a browser's back-then-navigate), and history never grows past capacity.

Signature

function useStateWithHistory(initialValue, { capacity = 10 }) {
  // returns [value, set, { history, pointer, back, forward, go }]
}

Examples

const [text, setText, { back, forward }] = useStateWithHistory('');
setText('h'); setText('hi'); // history: ['', 'h', 'hi']
back();   // text === 'h'  (undo)
forward(); // text === 'hi' (redo)
const [v, set, { go, history }] = useStateWithHistory(0);
set(1); set(2);
go(0);      // back to 0
set(9);     // branch: history is now [0, 9], the 1/2 future is discarded

Notes

  • Pointer into historyvalue is history[pointer]; back/forward move the pointer (clamped), go(i) jumps to it.
  • Branch on set-after-back — a set while the pointer is behind the end drops everything after it, then appends — you can't redo into the old future.
  • Capacity — cap the history length; drop the oldest entries when it would overflow.
  • Record only real changes — a set to the same value shouldn't add a duplicate history entry.
Loading editor…