🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

React Flow Architecture

by @anderskev

Architectural guidance for building node-based UIs with React Flow. Use when designing flow-based applications, making decisions about state management, inte...

Versionv1.1.1
Downloads460
Installs1
TERMINAL
clawhub install react-flow-architecture

πŸ“– About This Skill


name: react-flow-architecture description: Architectural guidance for building node-based UIs with React Flow. Use when designing flow-based applications, making decisions about state management, integration patterns, or evaluating whether React Flow fits a use case.

React Flow Architecture

When to Use React Flow

Good Fit

  • Visual programming interfaces
  • Workflow builders and automation tools
  • Diagram editors (flowcharts, org charts)
  • Data pipeline visualization
  • Mind mapping tools
  • Node-based audio/video editors
  • Decision tree builders
  • State machine designers
  • Consider Alternatives

  • Simple static diagrams (use SVG or canvas directly)
  • Heavy real-time collaboration (may need custom sync layer)
  • 3D visualizations (use Three.js, react-three-fiber)
  • Graph analysis with 10k+ nodes (use WebGL-based solutions like Sigma.js)
  • Decision workflow (gates)

    Run this sequence before locking the stack or sprinting implementation. Skip only for throwaway prototypes.

    1. Name the interactions β€” List the top user actions (e.g. drag, connect, delete, group). Pass: Each action maps to a concrete React Flow callback you will implement (onNodesChange, onConnect, …).

    2. Classify scale β€” Estimate peak nodes (visible canvas or document total). Pass: Your range matches a row in Node Count Guidelines and you accept the listed strategy (e.g. onlyRenderVisibleElements when that row implies it).

    3. Place state β€” Choose local hooks, an external store, or Redux/other. Pass: One sentence states where persistence, undo, or cross-surface sync will live, or explicitly β€œnot needed yet.”

    4. Re-check alternatives β€” If the use case matches Consider Alternatives, Pass: One sentence explains why React Flow still fits or which listed alternative you chose instead.

    Architecture Patterns

    Package Structure (xyflow)

    @xyflow/system (vanilla TypeScript)
    β”œβ”€β”€ Core algorithms (edge paths, bounds, viewport)
    β”œβ”€β”€ xypanzoom (d3-based pan/zoom)
    β”œβ”€β”€ xydrag, xyhandle, xyminimap, xyresizer
    └── Shared types

    @xyflow/react (depends on @xyflow/system) β”œβ”€β”€ React components and hooks β”œβ”€β”€ Zustand store for state management └── Framework-specific integrations

    @xyflow/svelte (depends on @xyflow/system) └── Svelte components and stores

    Implication: Core logic is framework-agnostic. When contributing or debugging, check if issue is in @xyflow/system or framework-specific package.

    State Management Approaches

    #### 1. Local State (Simple Apps)

    // useNodesState/useEdgesState for prototyping
    const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
    const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
    

    Pros: Simple, minimal boilerplate Cons: State isolated to component tree

    #### 2. External Store (Production)

    // Zustand store example
    import { create } from 'zustand';

    interface FlowStore { nodes: Node[]; edges: Edge[]; setNodes: (nodes: Node[]) => void; onNodesChange: OnNodesChange; }

    const useFlowStore = create((set, get) => ({ nodes: initialNodes, edges: initialEdges, setNodes: (nodes) => set({ nodes }), onNodesChange: (changes) => { set({ nodes: applyNodeChanges(changes, get().nodes) }); }, }));

    // In component function Flow() { const { nodes, edges, onNodesChange } = useFlowStore(); return ; }

    Pros: State accessible anywhere, easier persistence/sync Cons: More setup, need careful selector optimization

    #### 3. Redux/Other State Libraries

    // Connect via selectors
    const nodes = useSelector(selectNodes);
    const dispatch = useDispatch();

    const onNodesChange = useCallback((changes: NodeChange[]) => { dispatch(nodesChanged(changes)); }, [dispatch]);

    Data Flow Architecture

    User Input β†’ Change Event β†’ Reducer/Handler β†’ State Update β†’ Re-render
         ↓
    [Drag node] β†’ onNodesChange β†’ applyNodeChanges β†’ setNodes β†’ ReactFlow
         ↓
    [Connect]   β†’ onConnect β†’ addEdge β†’ setEdges β†’ ReactFlow
         ↓
    [Delete]    β†’ onNodesDelete β†’ deleteElements β†’ setNodes/setEdges β†’ ReactFlow
    

    Sub-Flow Pattern (Nested Nodes)

    // Parent node containing child nodes
    const nodes = [
      {
        id: 'group-1',
        type: 'group',
        position: { x: 0, y: 0 },
        style: { width: 300, height: 200 },
      },
      {
        id: 'child-1',
        parentId: 'group-1',  // Key: parent reference
        extent: 'parent',      // Key: constrain to parent
        position: { x: 10, y: 30 },  // Relative to parent
        data: { label: 'Child' },
      },
    ];
    

    Considerations:

  • Use extent: 'parent' to constrain dragging
  • Use expandParent: true to auto-expand parent
  • Parent z-index affects child rendering order
  • Viewport Persistence

    // Save viewport state
    const { toObject, setViewport } = useReactFlow();

    const handleSave = () => { const flow = toObject(); // flow.nodes, flow.edges, flow.viewport localStorage.setItem('flow', JSON.stringify(flow)); };

    const handleRestore = () => { const flow = JSON.parse(localStorage.getItem('flow')); setNodes(flow.nodes); setEdges(flow.edges); setViewport(flow.viewport); };

    Integration Patterns

    With Backend/API

    // Load from API
    useEffect(() => {
      fetch('/api/flow')
        .then(r => r.json())
        .then(({ nodes, edges }) => {
          setNodes(nodes);
          setEdges(edges);
        });
    }, []);

    // Debounced auto-save const debouncedSave = useMemo( () => debounce((nodes, edges) => { fetch('/api/flow', { method: 'POST', body: JSON.stringify({ nodes, edges }), }); }, 1000), [] );

    useEffect(() => { debouncedSave(nodes, edges); }, [nodes, edges]);

    With Layout Algorithms

    import dagre from 'dagre';

    function getLayoutedElements(nodes: Node[], edges: Edge[]) { const g = new dagre.graphlib.Graph(); g.setGraph({ rankdir: 'TB' }); g.setDefaultEdgeLabel(() => ({}));

    nodes.forEach((node) => { g.setNode(node.id, { width: 150, height: 50 }); });

    edges.forEach((edge) => { g.setEdge(edge.source, edge.target); });

    dagre.layout(g);

    return { nodes: nodes.map((node) => { const pos = g.node(node.id); return { ...node, position: { x: pos.x, y: pos.y } }; }), edges, }; }

    Performance Scaling

    Node Count Guidelines

    | Nodes | Strategy | |-------|----------| | < 100 | Default settings | | 100-500 | Enable onlyRenderVisibleElements | | 500-1000 | Simplify custom nodes, reduce DOM elements | | > 1000 | Consider virtualization, WebGL alternatives |

    Optimization Techniques

    // Reduce node border radius (improves intersect calculations) nodeExtent={[[-1000, -1000], [1000, 1000]]}

    // Disable features not needed elementsSelectable={false} panOnDrag={false} zoomOnScroll={false} />

    Trade-offs

    Controlled vs Uncontrolled

    | Controlled | Uncontrolled | |------------|--------------| | More boilerplate | Less code | | Full state control | Internal state | | Easy persistence | Need toObject() | | Better for complex apps | Good for prototypes |

    Connection Modes

    | Strict (default) | Loose | |------------------|-------| | Source β†’ Target only | Any handle β†’ any handle | | Predictable behavior | More flexible | | Use for data flows | Use for diagrams |

    
    

    Edge Rendering

    | Default edges | Custom edges | |---------------|--------------| | Fast rendering | More control | | Limited styling | Any SVG/HTML | | Simple use cases | Complex labels |