BoardStore
Mutable store with slice-based pub/sub for high-performance state management
BoardStore is a mutable state container that lives outside React's state management. It uses a slice-based publish/subscribe pattern, and hooks bridge it into React via useSyncExternalStore.
Why Not React State?
During rapid drawing (60+ pointer events per second), React's setState / useReducer would trigger excessive re-renders. By keeping the hot path (scene mutations, viewport updates) in a mutable store and only notifying relevant slices, the canvas renderer can update independently of React's reconciliation cycle.
BoardStoreState
interface BoardStoreState {
board: Board | null;
pages: Page[];
activePageId: string | null;
scene: SceneState;
viewport: ViewportState;
activeTool: string;
toolConfig: Record<string, unknown>;
selectedIds: Set<string>;
hoveredId: string | null;
history: History;
participants: Map<string, Participant>;
cursors: Map<string, CursorPosition>;
lastError: BoardError | null;
gridConfig: GridConfig;
}Store Slices
Each state mutation notifies a specific slice. Hooks subscribe to only the slices they need.
type StoreSlice =
| 'board'
| 'pages'
| 'scene'
| 'viewport'
| 'tool'
| 'selection'
| 'participants'
| 'cursors'
| 'error'
| 'grid';| Slice | Notified By |
|---|---|
board | setBoard() |
pages | setPages(), setActivePageId() |
scene | updateScene() |
viewport | updateViewport() |
tool | setActiveTool(), setToolConfig() |
selection | setSelection(), setHoveredId() |
participants | setParticipants() |
cursors | updateCursor(), removeCursor() |
error | setError() |
grid | setGridConfig() |
Methods
State Access
getState(): BoardStoreState;Board Data
setBoard(board: Board | null): void;
setPages(pages: Page[]): void;
setActivePageId(pageId: string | null): void;Scene
updateScene(fn: (scene: SceneState) => SceneState): void;The update function receives the current scene and must return a new scene. This is where all element mutations happen.
Viewport
updateViewport(fn: (vp: ViewportState) => ViewportState): void;Tool
setActiveTool(toolId: string): void;
setToolConfig(config: Record<string, unknown>): void;Selection
setSelection(ids: Set<string>): void;
setHoveredId(id: string | null): void;Collaboration
setParticipants(participants: Map<string, Participant>): void;
updateCursor(userId: string, cursor: CursorPosition): void;
removeCursor(userId: string): void;Grid
setGridConfig(config: Partial<GridConfig>): void;Error
setError(error: BoardError | null): void;Subscriptions
subscribe(slice: string, listener: () => void): () => void;Returns an unsubscribe function. The listener is called synchronously whenever the specified slice is notified.
Usage from Hooks
// This is how hooks bridge the store into React:
import { useSyncExternalStore } from 'react';
function useGridConfig() {
const { store } = useBoardKit();
return useSyncExternalStore(
(cb) => store.subscribe('grid', cb),
() => store.getState().gridConfig,
);
}