Building a Zustand Undo/Redo State Manager for React with AI

Undo/Redo functionality — the Ctrl+Z experience — seems simple on the surface but hides significant architectural complexity. The naive approach (keeping a full copy of state in a history array) quickly becomes a memory and performance problem. The correct approach uses a temporal state architecture that tracks state diffs, not full snapshots.
Zustand makes this achievable with its middleware system. The temporal middleware from the zundo package wraps your store and automatically maintains a history of state changes, exposing undo, redo, clear, and time-travel APIs. In this guide, we use AI prompts to generate the complete implementation.
The Three Approaches to Undo/Redo in React
| Approach | Pros | Cons |
|---|---|---|
| Full snapshot history | Simple to implement | Memory-intensive for large state |
| Diff-based (immer patches) | Memory efficient | Complex patch logic |
| Zundo temporal middleware | Best of both: configurable partialize | Requires zundo dependency |
Prompt 1: Canvas Editor with Full Undo/Redo
"Act as a Senior React State Management Engineer. Using Zustand with the 'zundo' temporal middleware, build a canvas editor state store with full undo/redo support. Requirements: (1) The store manages: elements (array of canvas objects with id, type, x, y, width, height, color), selectedElementId, and zoom level. (2) Only element changes should be undoable — NOT selectedElementId or zoom changes. Use zundo's partialize option to exclude these from history. (3) Implement these actions: addElement, updateElement, deleteElement, moveElement, and batchMove (moves multiple elements). (4) Implement undo, redo, clearHistory, and getHistory as store actions. (5) Limit history to 50 steps using the limit option. (6) Create a useHotkeys hook that binds Ctrl+Z to undo and Ctrl+Shift+Z or Ctrl+Y to redo. (7) Full TypeScript types for all element types. Show complete store file."Prompt 2: Text Editor with Undo Stack Management
"Write a Zustand text editor store with a custom undo/redo implementation (without zundo) using a circular buffer for memory efficiency. Requirements: (1) State: content (string), cursorPosition (number), and historyBuffer (circular array of max 100 entries with head/tail pointers). (2) Every content change under debouncing of 300ms should create a new history entry. (3) Implement 'record' action that adds current state snapshot to the history buffer. (4) Implement undo/redo by navigating the buffer. (5) Collapsed consecutive typing into single history entries (typing 'hello' = 1 undo step, not 5). (6) Detect 'significant' changes (paste, delete selection, format change) and immediately record to history even before the debounce timer fires. (7) Export historySize (current entries), canUndo, and canRedo as computed derived values. Full TypeScript."Prompt 3: Visual History Timeline Component
"Create a React TypeScript component 'HistoryTimeline' that visualizes the undo/redo history from a Zustand temporal store. Requirements: (1) Display the last 20 history entries as a vertical timeline. (2) The current state is highlighted with a primary color indicator. (3) Past states (can undo) appear in full opacity, future states (can redo) appear at 40% opacity. (4) Each entry shows: an icon representing the action type (add, delete, move, edit), a human-readable label, and a timestamp. (5) Clicking any entry in the timeline time-travels to that state using setTemporalState. (6) Add a 'Jump to here' tooltip on hover. (7) Animate the timeline using CSS transitions when new entries are added. Use Tailwind CSS for styling. Include proper ARIA labels for accessibility."Pro Tips for Production Undo/Redo
⚠️ Debounce Rapid Changes
Without debouncing, each individual keystroke creates a new history entry. Pressing Ctrl+Z 10 times would only undo 10 characters — not the whole word. Debounce text changes at 300–500ms so that rapid consecutive changes are grouped into single history entries before being committed to the undo stack.
💡 Don't Undo UI State — Only Data State
Users expect Ctrl+Z to undo content changes — not UI state changes like modal open/close, tab selection, or zoom level. Use zundo's partialize function to carefully define exactly which keys in your store are included in the undo history.
Frequently Asked Questions
Q: Is zundo stable enough for production?
A: Yes. zundo is actively maintained, TypeScript-first, and used in production by multiple open-source projects including canvas editors and form builders. It has excellent test coverage and supports Zustand v4+. The API was stabilized in zundo v2 with no breaking changes expected in v2.x.
Q: How do I handle undo/redo for asynchronous operations?
A: This is a genuinely hard problem. The standard approach is to not undo the async operation itself (you can't "un-send" an API call), but to track the optimistic UI state and undo that. If the async operation creates data on the server, add a "reverse" operation to the undo stack that makes a compensating API call (e.g., undo "create item" triggers "delete item").
Q: Can I persist the undo history across page refreshes?
A: Yes, but be cautious. Persisting undo history to localStorage increases storage requirements significantly. Use zundo with Zustand's persist middleware, but set a maximum history size and use a custom serialization that compresses state diffs rather than storing full snapshots. Most products don't persist undo history — they clear it on page reload, which is the expected behavior.
Naveen Teja Palle
Frontend Architect · State Management Engineer
React engineer specialized in complex state management patterns. Has implemented production undo/redo systems for canvas editors, document editors, and form builders.
100+ Zustand & State Management Prompts
Middleware patterns, slice patterns, persistence, and more — built for production React applications.
Explore React State Prompts →