Understanding the UndoRedoContext in drawdb-io/drawdb: How the Diagram Editor Manages Action History

The UndoRedoContext in DrawDB is a React Context API implementation that maintains two state stacks—undo and redo—to enable users to revert and reapply changes in the database diagram editor.

Managing state history is essential for any visual editing tool. In DrawDB, an open-source database schema design tool, the UndoRedoContext provides a centralized, hook-based API that any component can use to track diagram modifications, restore previous states, and rebuild the editor's history.

Core Architecture of the UndoRedoContext

The context is defined in src/context/UndoRedoContext.jsx and exposes a provider component that wraps the entire editor application. Internally, it maintains:

  • Undo stack: Stores previous diagram states in LIFO order
  • Redo stack: Stores states that were popped from the undo stack and can be restored
  • Status flags: Boolean indicators for UI button states

The state management follows the command pattern for history—each user action pushes a snapshot of the previous state onto the undo stack, while clearing the redo stack (new actions invalidate any previously undone history).

Available Operations in the UndoRedoContext API

Function Description
push(state) Captures the current diagram state, pushes it to the undo stack, and clears the redo stack
undo() Pops the most recent state from undo, pushes current state to redo, and returns the previous diagram state
redo() Pops the most recent state from redo, pushes current state to undo, and returns the redone diagram state
clear() Resets both stacks to empty—invoked when loading a new diagram or resetting the editor
canUndo Boolean flag: true when undo stack contains items
canRedo Boolean flag: true when redo stack contains items

These operations ensure that every change—adding tables, editing columns, moving entities, or modifying relationships—can be recorded and reversed atomically.

Using the useUndoRedo Hook in Components

The custom hook useUndoRedo in src/hooks/useUndoRedo.js provides the consumer interface. Any component within the provider tree can destructure the API and status flags.

Wiring Undo/Redo buttons in a toolbar:

import { useUndoRedo } from '@/hooks/useUndoRedo';

function Toolbar() {
  const { undo, redo, canUndo, canRedo } = useUndoRedo();

  return (
    <div className="toolbar">
      <button onClick={undo} disabled={!canUndo}>
        Undo
      </button>
      <button onClick={redo} disabled={!canRedo}>
        Redo
      </button>
    </div>
  );
}

Recording state before diagram modifications:

import { useUndoRedo } from '@/hooks/useUndoRedo';
import { useDiagram } from '@/hooks/useDiagram';

function AddTableButton() {
  const { diagram, setDiagram } = useDiagram();
  const { push } = useUndoRedo();

  const handleAddTable = () => {
    const newDiagram = /* logic to add a table */;
    push(diagram);            // Preserve current state before mutation
    setDiagram(newDiagram);   // Apply the update
  };

  return <button onClick={handleAddTable}>Add Table</button>;
}

The push() call must precede the state update to ensure the undo stack captures the pre-change snapshot correctly.

Integration with the Editor Application

According to the drawdb source code, the UndoRedoContext.Provider wraps the main editor in src/pages/Editor.jsx. This placement makes the history API available to:

  • Toolbar components for button controls
  • Canvas components for detecting changes
  • Properties panels for field edits
  • Keyboard shortcut handlers (Ctrl+Z, Ctrl+Shift+Z)

The UndoRedoContext works in conjunction with useDiagram from src/hooks/useDiagram.js, which manages the live diagram state. When push() stores a snapshot, that snapshot is a complete diagram object that setDiagram() can later restore.

Key Implementation Files

File Role in Undo-Redo System
src/context/UndoRedoContext.jsx Defines the React context, provider logic, and stack-based state management
src/hooks/useUndoRedo.js Exports the useUndoRedo hook for consuming components
src/pages/Editor.jsx Mounts the UndoRedoContext.Provider at the root of the editor
src/hooks/useDiagram.js Manages current diagram state; coordinates with undo-redo for snapshots

Summary

  • The UndoRedoContext in DrawDB implements a dual-stack history system using React Context API
  • push(state), undo(), redo(), and clear() provide complete history control
  • useUndoRedo hook enables any descendant component to access the API and canUndo/canRedo flags
  • The provider wraps src/pages/Editor.jsx, making history available throughout the application
  • Integration with useDiagram ensures diagram state snapshots are captured and restored atomically

Frequently Asked Questions

How does the UndoRedoContext handle state immutability?

The context expects immutable diagram state objects. When push(diagram) is called, it stores a reference to the current state before mutation. Modifications should create new objects rather than mutate existing ones to prevent history corruption.

Where should I place the push() call in my component?

Always call push() before applying state changes, as shown in handleAddTable() above. Calling after setDiagram() would save the new state rather than the previous one, making undo restore the same state instead of reverting the change.

Can I use useUndoRedo outside the Editor page?

No—the hook throws an error if called outside a UndoRedoContext.Provider. The provider is currently mounted only in src/pages/Editor.jsx, so components in other routes cannot access undo-redo functionality unless wrapped with an additional provider instance.

What happens to the redo stack when I push a new state?

The redo stack is cleared immediately on any push() call. This matches standard editor behavior: once you make a new change after undoing, the alternate timeline of redone states is discarded and replaced with the new forward history.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →