# DiagramContext for Managing Database Elements: Architecture and State Management in DrawDB

> Explore the DiagramContext architecture in DrawDB. Learn how this React Context provider manages database element state with efficient CRUD operations for tables, relationships, and fields.

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

---

**DrawDB's DiagramContext serves as the central state container for all diagram data, exposing CRUD operations for tables, relationships, and fields through a React Context provider located in [`src/context/DiagramContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx).**

DrawDB is an open-source entity-relationship diagramming tool that relies on a centralized state architecture to manage complex database schemas. The **DiagramContext for managing database elements** provides a single source of truth for tables, relationships, and metadata, decoupling data logic from the presentation layer. By leveraging React's Context API combined with custom hooks like `useDiagram`, the application maintains a mutable yet predictable state that supports undo/redo functionality and persistent storage.

## Core State Structure

The provider initializes the diagram's mutable state using standard React `useState` hooks. State variables include the `tables` array, `relationships` array, the selected `database` type, and a unique `diagramId`.

According to the [DrawDB source code](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L30-L38), the core state declarations appear as:

- **`const [tables, setTables] = useState([])`** – Stores the array of table objects, each containing fields, constraints, and metadata.
- **`const [relationships, setRelationships] = useState([])`** – Maintains connections between tables, including foreign key references.
- **`const [database, setDatabase] = useState(null)`** – Tracks the target SQL dialect (PostgreSQL, MySQL, etc.).

These state objects form the foundation for all subsequent CRUD operations within the application.

## CRUD Operations for Tables and Relationships

DiagramContext exposes a rich API for modifying database elements without direct state manipulation. The helper functions wrap `setTables` and `setRelationships` to ensure immutable updates.

The [CRUD helpers](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L74-L115) include:

- **`addTable(table)`** – Appends a new table object to the existing array using the spread operator.
- **`updateTable(tableId, updates)`** – Maps over the tables array to merge updates into the matching entry.
- **`deleteTable(tableId)`** – Filters the array to remove the specified table and cascades deletion to associated relationships.
- **`addRelationship(rel)`**, **`updateRelationship(id, rel)`**, and **`deleteRelationship(id)`** – Manage the connections between entities with similar immutable patterns.

Each mutation automatically registers with the `UndoRedoContext` to enable history traversal.

## Field-Level State Management

Nested field data lives inside table objects, requiring specialized helpers to modify specific columns without replacing entire table definitions.

As implemented in [lines 119–146](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L119-L146), the context provides:

- **`addField(tableId, field)`** – Pushes a new field object into the target table's `fields` array.
- **`updateField(tableId, field)`** – Replaces an existing field by matching `field.id` within the parent table.
- **`deleteField(tableId, fieldId)`** – Filters the `fields` array to remove the specified column.

These methods ensure that atomic updates to column definitions (renaming, type changes, constraint toggles) do not trigger unnecessary re-renders of unrelated tables.

## Undo/Redo Integration

Every mutating action within DiagramContext is registered with the `UndoRedoContext` to maintain a reversible history stack.

The [undo integration logic](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L154-L178) wraps each state update in an `undoRedo.add()` call, passing `undo` and `redo` functions that capture the previous and next states. This design allows users to reverse operations like accidental table deletion or field modification without implementing complex state diffing algorithms.

The `UndoRedoContext` itself is defined in [`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx) and consumed via the `useUndoRedo` hook.

## Persistence and Data Loading

DiagramContext handles hydration from IndexedDB and cloud sources, as well as serializing state changes back to storage.

The [persistence logic](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L280-L340) includes:

- **`loadDiagram(id)`** – Retrieves diagram JSON from `db.diagrams` (wrapped in [`src/data/db.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/db.js)) or cloud extensions via `extensions.cloudLoad`.
- **`applyDiagramState(state)`** – Validates and merges loaded data into the reactive state variables.
- **`saveDiagram()`** – Persists the current `tables`, `relationships`, and `database` values to IndexedDB with versioning.

This architecture ensures that diagram data survives page refreshes while supporting offline-first functionality.

## Selection and Canvas Integration

Interactivity features like multi-select and coordinate translation integrate with `CanvasContext` through DiagramContext.

According to [lines 340–360](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L340-L360), the provider maintains:

- **`selectedIds`** and **`setSelectedIds`** – Arrays tracking which tables or relationships are currently highlighted.
- **`toDiagramSpace(x, y)`** – Utility imported from `useCanvas` ([`src/hooks/useCanvas.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useCanvas.js)) that converts screen coordinates to diagram coordinates for precise element positioning.

The provider renders the context value at [lines 318–340](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx#L318-L340), making the full API available to descendant components.

## Accessing DiagramContext in Components

Components consume the context through the `useDiagram` hook defined in [`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js), which simply calls `useContext(DiagramContext)`.

### Reading Diagram State

```jsx
import { useDiagram } from '../../hooks';

function TablesPanel() {
  const { tables, database } = useDiagram();

  return (
    <div>
      <h2>{database} Schema</h2>
      {tables.map(t => (
        <div key={t.id}>{t.name} ({t.fields.length} fields)</div>
      ))}
    </div>
  );
}

```

### Modifying Tables and Fields

```jsx
import { useDiagram } from '../../hooks';
import { uid } from '../../utils';

function AddFieldButton({ tableId }) {
  const { updateField } = useDiagram();

  const handleAdd = () => {
    const newField = { 
      id: uid(), 
      name: 'created_at', 
      type: 'timestamp', 
      nullable: false 
    };
    updateField(tableId, newField);
  };

  return <button onClick={handleAdd}>Add Timestamp</button>;
}

```

### Using Undo/Redo

```jsx
import { useUndoRedo } from '../../hooks';

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

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

```

## Summary

- **DiagramContext** in [`src/context/DiagramContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/DiagramContext.jsx) centralizes all diagram state including tables, relationships, and database type selection.
- The context exposes immutable **CRUD helpers** for tables, relationships, and nested fields, ensuring predictable state updates.
- **Undo/Redo functionality** is integrated at the mutation level through `UndoRedoContext`, wrapping every change in reversible actions.
- **Persistence logic** handles loading from and saving to IndexedDB via [`src/data/db.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/db.js), with optional cloud storage extensions.
- Components access this architecture through the **`useDiagram`** hook, maintaining clean separation between data management and UI rendering.

## Frequently Asked Questions

### How does DiagramContext persist diagram data between sessions?

DiagramContext automatically saves state changes to the browser's IndexedDB using the `db.diagrams` object defined in [`src/data/db.js`](https://github.com/drawdb-io/drawdb/blob/main/src/data/db.js). The `saveDiagram` function serializes the current `tables`, `relationships`, and `database` values, while `loadDiagram` hydrates the application state from storage or cloud sources on initialization.

### What is the relationship between DiagramContext and UndoRedoContext?

DiagramContext depends on `UndoRedoContext` (defined in [`src/context/UndoRedoContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/UndoRedoContext.jsx)) to maintain a history stack of state changes. Every mutating function in DiagramContext registers an `undo` and `redo` action before applying updates, enabling users to reverse operations without implementing manual state snapshots in each component.

### How can I access diagram data in a custom React component?

Import the `useDiagram` hook from [`src/hooks/useDiagram.js`](https://github.com/drawdb-io/drawdb/blob/main/src/hooks/useDiagram.js) and call it inside any component that is a descendant of the `DiagramContext.Provider`. This hook returns the entire context value including state arrays (`tables`, `relationships`) and mutating functions (`addTable`, `updateField`, etc.), allowing direct interaction with the diagram model.

### Where are table fields stored in the state architecture?

Table fields exist as nested arrays inside the `tables` state object within DiagramContext. Each table object contains a `fields` property, and the context provides specialized helpers like `addField`, `updateField`, and `deleteField` to modify these nested structures immutably without replacing the entire table definition.