# How DrawDB Implements Undo/Redo Functionality in the Diagram Editor

> Explore drawdb undo/redo functionality in the diagram editor. Learn how DrawDB uses a command pattern with state stacks and a custom React hook for seamless history management.

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

---

**DrawDB manages diagram editing history using a command-pattern approach with dual state stacks wrapped in a custom React hook and context provider.**

DrawDB is an open-source database entity-relationship diagram editor that provides robust **undo/redo functionality** to ensure users can safely experiment with database designs. The implementation relies on a centralized state management system that captures snapshots of the entire diagram after every mutation. This article examines the architecture, key source files, and practical integration patterns that make the feature work seamlessly across the application.

## The Command Pattern Architecture

The undo/redo system follows the classic **command pattern** by maintaining two distinct history stacks. This design ensures linear edit history and prevents complex branching states that could confuse users.

### State History Stacks

At the core of the system are two arrays:

- **Undo stack**: Stores previous diagram states, allowing users to step backward through their editing history.
- **Redo stack**: Stores states that have been undone, enabling users to step forward again.

When a user performs an action—such as adding a table, moving an element, or modifying a relationship—the current diagram state is pushed onto the undo stack. Simultaneously, the redo stack is cleared to maintain a linear history. This behavior matches user expectations in modern design applications.

### State Snapshot Management

The system exposes four primary functions through the context:

- **`undo()`**: Pops the latest state from the undo stack, pushes it to the redo stack, and restores the editor to that previous state.
- **`redo()`**: Pops the latest state from the redo stack, pushes it back onto the undo stack, and updates the diagram accordingly.
- **`addState(state)`**: Captures a new diagram snapshot and adds it to the undo stack. Called after every mutation.
- **`clearHistory()`**: Resets both stacks, typically invoked when loading a new diagram file.

## Core Implementation Files

### useUndoRedo Hook

The business logic resides in **[`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js)**. This custom React hook maintains the dual stack structure using React state or refs and returns the four core functions along with boolean flags indicating whether undo or redo actions are available.

### UndoRedoContext Provider

To make the undo/redo API accessible throughout the component tree without prop drilling, DrawDB wraps the application in **`UndoRedoContext`** defined in **[`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx)**. This context provider instantiates the hook and passes its return values to all child components.

### Workspace Integration

The context is provided at the top level of the editor in **[`src/components/Workspace.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/Workspace.jsx)**. This ensures that every diagram-related component—from the canvas to the property panels—can access the undo/redo functions via the `useContext` hook.

## Keyboard Shortcuts and Mutation Tracking

### useDiagram Hook Integration

All diagram mutations are funneled through **[`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js)**, which serves as the primary interface for modifying diagram state. After every mutation (adding tables, updating relationships, repositioning elements), this hook calls **`addState()`** to capture the new state.

This tight coupling ensures the history stack remains synchronized with the visual representation on screen.

### Keyboard Event Handling

Keyboard shortcuts are registered within the same **[`useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/useDiagram.js)** file. The system listens for:

- **Ctrl + Z**: Triggers `undo()`
- **Ctrl + Shift + Z**: Triggers `redo()`

These event listeners prevent default browser behavior and call the appropriate context functions, providing the standard editing experience users expect from desktop design applications.

## Practical Usage Example

Components consume the undo/redo functionality through React context. Below is a typical implementation pattern for a toolbar component that both triggers mutations and provides undo/redo buttons:

```jsx
import { useContext } from 'react';
import { UndoRedoContext } from '../context/UndoRedoContext';
import { DiagramContext } from '../context/DiagramContext';

function ToolbarButtons() {
  const { undo, redo, addState } = useContext(UndoRedoContext);
  const { diagram, setDiagram } = useContext(DiagramContext);

  const addTable = () => {
    const newTable = createEmptyTable();
    const newDiagram = { 
      ...diagram, 
      tables: [...diagram.tables, newTable] 
    };
    setDiagram(newDiagram);
    addState(newDiagram);
  };

  return (
    <div className="toolbar">
      <button onClick={undo}>Undo (Ctrl+Z)</button>
      <button onClick={redo}>Redo (Ctrl+Shift+Z)</button>
      <button onClick={addTable}>Add Table</button>
    </div>
  );
}

```

The keyboard shortcut registration typically appears in the main diagram hook:

```javascript
// Inside src/hooks/useDiagram.js
useEffect(() => {
  const handleKeyDown = (e) => {
    if (e.ctrlKey && !e.shiftKey && e.key === 'z') {
      e.preventDefault();
      undo();
    }
    if (e.ctrlKey && e.shiftKey && e.key === 'Z') {
      e.preventDefault();
      redo();
    }
  };
  
  window.addEventListener('keydown', handleKeyDown);
  return () => window.removeEventListener('keydown', handleKeyDown);
}, [undo, redo]);

```

## Summary

- DrawDB implements **undo/redo functionality** using a command-pattern architecture with dual state stacks maintained in **[`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js)**.
- The **`UndoRedoContext`** in **[`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx)** exposes `undo()`, `redo()`, `addState()`, and `clearHistory()` to the entire application.
- All diagram mutations flow through **[`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js)**, which automatically captures state snapshots after every change.
- Keyboard shortcuts (Ctrl+Z for undo, Ctrl+Shift+Z for redo) are handled in **[`useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/useDiagram.js)** and integrated with the context methods.
- The context provider wraps the editor in **[`src/components/Workspace.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/Workspace.jsx)**, ensuring consistent history management across all diagram editing actions.

## Frequently Asked Questions

### How does DrawDB store undo history?

DrawDB stores undo history using two in-memory arrays (stacks) maintained by the **`useUndoRedo`** hook. The undo stack holds previous diagram states, while the redo stack holds states that have been undone. When the user performs a new action, the current state is pushed onto the undo stack and the redo stack is cleared.

### What happens when I press Ctrl+Z in DrawDB?

When you press **Ctrl+Z**, the keyboard event handler in **[`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js)** intercepts the shortcut and calls the **`undo()`** function from **`UndoRedoContext`**. This pops the most recent state from the undo stack, pushes it onto the redo stack, and restores the diagram to that previous configuration.

### Can I redo an action after undoing in DrawDB?

Yes. DrawDB supports **redo** via **Ctrl+Shift+Z**. When triggered, the **`redo()`** function pops the state from the redo stack and pushes it back onto the undo stack, effectively moving forward in the edit history. The redo stack is only available until you perform a new mutation, which clears it.

### Where is the undo/redo logic defined in the DrawDB codebase?

The core logic resides in **[`src/hooks/useUndoRedo.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useUndoRedo.js)**, while the React context provider is located in **[`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx)**. The integration with the diagram editor happens in **[`src/components/Workspace.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/Workspace.jsx)** (provider placement) and **[`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js)** (mutation tracking and keyboard shortcuts).