DiagramContext for Managing Database Elements: Architecture and State Management in DrawDB
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.
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, 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 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), anddeleteRelationship(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, the context provides:
addField(tableId, field)– Pushes a new field object into the target table'sfieldsarray.updateField(tableId, field)– Replaces an existing field by matchingfield.idwithin the parent table.deleteField(tableId, fieldId)– Filters thefieldsarray 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 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 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 includes:
loadDiagram(id)– Retrieves diagram JSON fromdb.diagrams(wrapped insrc/data/db.js) or cloud extensions viaextensions.cloudLoad.applyDiagramState(state)– Validates and merges loaded data into the reactive state variables.saveDiagram()– Persists the currenttables,relationships, anddatabasevalues 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, the provider maintains:
selectedIdsandsetSelectedIds– Arrays tracking which tables or relationships are currently highlighted.toDiagramSpace(x, y)– Utility imported fromuseCanvas(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, 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, which simply calls useContext(DiagramContext).
Reading Diagram State
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
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
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.jsxcentralizes 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, with optional cloud storage extensions. - Components access this architecture through the
useDiagramhook, 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. 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) 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →