Skip to main content
Version: 4.3

External State Management

Controlled mode means your state owns the cells, and that state can live in any store. The wiring never changes:

  1. read the cells from your store,
  2. pass them as cells,
  3. forward the store's setter as onCellsChange.

Every example below shares the same editor.tsx with the diagram, node, and toolbar. Only App.tsx, the store, changes. Open both tabs to see how little each library needs.

Zustand​

A create() store holds the cells; a plain setCells action replaces them. Read both with selector hooks and wire them straight in.

import { create } from 'zustand';
import { TaskDiagram, INITIAL_CELLS, type Cells } from './editor';

// The store owns the cells. `setCells` is a plain action that replaces them.
const useTaskStore = create<{ cells: Cells; setCells: (next: Cells) => void }>((set) => ({
  cells: INITIAL_CELLS,
  setCells: (next) => set({ cells: next }),
}));

export default function App() {
  const cells = useTaskStore((state) => state.cells);
  const setCells = useTaskStore((state) => state.setCells);

  // Read `cells`, forward the setter as `onCellsChange`. That's the wiring.
  return <TaskDiagram cells={cells} setCells={setCells} store="zustand" />;
}


Redux​

A Redux Toolkit slice owns the cells; the setCells reducer replaces them. Select the cells, and dispatch the reducer from onCellsChange.

import { configureStore, createSlice, type PayloadAction } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
import { useCallback } from 'react';
import { TaskDiagram, INITIAL_CELLS, type Cells } from './editor';

// A slice owns the cells; `setCells` is the reducer that replaces them.
const tasks = createSlice({
  name: 'tasks',
  initialState: { cells: INITIAL_CELLS },
  reducers: {
    setCells: (_state, action: PayloadAction<Cells>) => ({ cells: action.payload }),
  },
});

const store = configureStore({ reducer: { tasks: tasks.reducer } });
type RootState = ReturnType<typeof store.getState>;

function TaskEditor() {
  const cells = useSelector((state: RootState) => state.tasks.cells);
  const dispatch = useDispatch();

  // Dispatch the reducer from `onCellsChange`.
  const setCells = useCallback((next: Cells) => dispatch(tasks.actions.setCells(next)), [dispatch]);

  return <TaskDiagram cells={cells} setCells={setCells} store="redux" />;
}

export default function App() {
  return (
    <Provider store={store}>
      <TaskEditor />
    </Provider>
  );
}


Deltas often scale better

These demos replace the whole array through onCellsChange to stay identical. For anything larger, onIncrementalCellsChange is usually the better fit in any store: apply just the { added, changed, removed } delta instead of rebuilding the list on every change. Redux Toolkit especially, since its reducers run on an Immer draft, so you can mutate just the affected cells and let Immer produce the next immutable state for you.

Jotai​

One atom holds the cells. useAtom returns a [value, setValue] pair, just like useState, so the setter drops directly into onCellsChange.

import { atom, useAtom } from 'jotai';
import { TaskDiagram, INITIAL_CELLS, type Cells } from './editor';

// One atom holds the cells. `useAtom` gives back a `[value, setValue]` pair,
// exactly like `useState`, so the setter drops straight into `onCellsChange`.
const cellsAtom = atom<Cells>(INITIAL_CELLS);

export default function App() {
  const [cells, setCells] = useAtom(cellsAtom);

  return <TaskDiagram cells={cells} setCells={setCells} store="jotai" />;
}


Any store works​

There's nothing special about these three. GraphProvider only needs a cells array and an onCellsChange callback, so any state manager (MobX, Valtio, XState, TanStack Store, React Context, or a plain useReducer) drives the graph the same way: expose the cells, hand back the setter. If it can hold an array and notify on change, it can own a JointJS graph.

Want granular, reducer-friendly events instead of full-array replacement? Pair any of these with onIncrementalCellsChange.

Stay in the know

Be where thousands of diagramming enthusiasts meet

Star us on GitHub