Flow Studio
Subscribing to Slices
Components subscribe to workflowStore slices using selector functions. Narrow selectors and shallow equality prevent unnecessary re-renders when unrelated parts of the store change.
Basic Selector Pattern
import { useWorkflowStore } from '@flow-studio/services';
// Subscribe to just the nodes array
function NodeCountBadge() {
const nodeCount = useWorkflowStore(state => state.nodes.length);
return <span>{nodeCount} nodes</span>;
// Re-renders only when nodes.length changes
}
// Subscribe to multiple fields — use shallow equality
import { shallow } from 'zustand/shallow';
function CanvasToolbar() {
const { zoomIn, zoomOut, fitView } = useWorkflowStore(
state => ({ zoomIn: state.zoomIn, zoomOut: state.zoomOut, fitView: state.fitView }),
shallow
);
// Re-renders only when these action references change (they don't — stable)
}
Selecting a Single Node
// Subscribe to a specific node by ID
function NodeConfigPanel({ nodeId }: { nodeId: string }) {
const node = useWorkflowStore(
state => state.nodes.find(n => n.id === nodeId)
);
// Re-renders when ANY node changes — but find is cheap
if (!node) return null;
return <ConfigForm node={node} />;
}
// Better for performance: memoize the selector
import { useMemo } from 'react';
function NodeConfigPanel({ nodeId }: { nodeId: string }) {
const selector = useMemo(
() => (state: WorkflowState) => state.nodes.find(n => n.id === nodeId),
[nodeId]
);
const node = useWorkflowStore(selector);
// ...
}
Subscribing to Actions Only
// Actions are stable references — no equality check needed
function NodePalette() {
const addNode = useWorkflowStore(state => state.addNode);
// addNode is stable — this component never re-renders due to store changes
function handleDrop(nodeType: string, position: XYPosition) {
addNode({ id: crypto.randomUUID(), type: nodeType, position, data: getDefaultData(nodeType) });
}
return <PaletteList onDrop={handleDrop} />;
}
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
useWorkflowStore() with no selector | Re-renders on every store change | Always provide a selector |
state => ({ nodes: state.nodes, edges: state.edges }) without shallow | New object reference every render | Add shallow as second arg |
getState().nodes.push(...) | Mutates state directly — bypasses reactivity | Use addNode action |
Outside React — Vanilla Subscriptions
// Subscribe from outside a component (e.g., in a service or event handler)
const unsubscribe = useWorkflowStore.subscribe(
state => state.nodes,
(nodes) => {
console.log('Nodes changed:', nodes.length);
},
{ equalityFn: shallow }
);
// Clean up
unsubscribe();