Portal Community

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-PatternProblemFix
useWorkflowStore() with no selectorRe-renders on every store changeAlways provide a selector
state => ({ nodes: state.nodes, edges: state.edges }) without shallowNew object reference every renderAdd shallow as second arg
getState().nodes.push(...)Mutates state directly — bypasses reactivityUse 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();