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

> Explore the UndoRedoContext in drawdb-io/drawdb. Learn how this React Context manages undo and redo stacks to enable seamless action history for your diagram editor.

- Repository: [drawDB/drawdb](https://github.com/drawdb-io/drawdb)
- Tags: internals
- Published: 2026-08-14

---

**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`](https://github.com/drawdb-io/drawdb/blob/main/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`](https://github.com/drawdb-io/drawdb/blob/main/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:**

```jsx
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:**

```jsx
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`](https://github.com/drawdb-io/drawdb/blob/main/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`](https://github.com/drawdb-io/drawdb/blob/main/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`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx)**	| Defines the React context, provider logic, and stack-based state management |
| **[`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js)**	| Exports the `useUndoRedo` hook for consuming components |
| **[`src/pages/Editor.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/pages/Editor.jsx)**	| Mounts the `UndoRedoContext.Provider` at the root of the editor |
| **[`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/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`](https://github.com/drawdb-io/drawdb/blob/main/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`](https://github.com/drawdb-io/drawdb/blob/main/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.