Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 126% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 153% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 164% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 334% | 0% |
Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.
Create basic interactive graph:
tsximport ReactFlow, { Node, Edge } from "reactflow"; const nodes: Node[] = [ { id: "1", position: { x: 0, y: 0 }, data: { label: "Node 1" } }, { id: "2", position: { x: 100, y: 100 }, data: { label: "Node 2" } }, ]; const edges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }]; export default function Graph() { return <ReactFlow nodes={nodes} edges={edges} />; }
Build expandable/collapsible tree structures with parent-child relationships.
typescriptinterface TreeNode extends Node { data: { label: string; level: number; hasChildren: boolean; isExpanded: boolean; childCount: number; category: "root" | "category" | "process" | "detail"; }; }
typescriptconst buildVisibleNodes = useCallback( (allNodes: TreeNode[], expandedIds: Set<string>, otherDeps: any[]) => { const visibleNodes = new Map<string, TreeNode>(); const visibleEdges = new Map<string, TreeEdge>(); // Start with root nodes const rootNodes = allNodes.filter((n) => n.data.level === 0); // Recursively add visible nodes const addVisibleChildren = (node: TreeNode) => { visibleNodes.set(node.id, node); if (expandedIds.has(node.id)) { const children = allNodes.filter((n) => n.parentNode === node.id); children.forEach((child) => addVisibleChildren(child)); } }; rootNodes.forEach((root) => addVisibleChildren(root)); return { nodes: Array.from(visibleNodes.values()), edges: Array.from(visibleEdges.values()), }; }, [], );
Handle large datasets with incremental rendering and memoization.
typescriptconst useIncrementalGraph = ( allNodes: Node[], allEdges: Edge[], expandedList: string[], ) => { const prevExpandedListRef = useRef<Set<string>>(new Set()); const prevOtherDepsRef = useRef<any[]>([]); const { visibleNodes, visibleEdges } = useMemo(() => { const currentExpandedSet = new Set(expandedList); const prevExpandedSet = prevExpandedListRef.current; // Check if expanded list changed const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet); // Check if other dependencies changed const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current); if (expandedChanged && !otherDepsChanged) { // Only expanded list changed - incremental update return buildIncrementalUpdate( cachedVisibleNodesRef.current, cachedVisibleEdgesRef.current, allNodes, allEdges, currentExpandedSet, prevExpandedSet, ); } else { // Full rebuild needed return buildFullGraph(allNodes, allEdges, currentExpandedSet); } }, [allNodes, allEdges, expandedList, ...otherDeps]); return { visibleNodes, visibleEdges }; };
typescript// Memoize node components to prevent unnecessary re-renders const ProcessNode = memo(({ data, selected }: NodeProps) => { return ( <div className={`process-node ${selected ? 'selected' : ''}`}> {data.label} </div> ); }, (prevProps, nextProps) => { // Custom comparison function return ( prevProps.data.label === nextProps.data.label && prevProps.selected === nextProps.selected && prevProps.data.isExpanded === nextProps.data.isExpanded ); }); // Memoize edge calculations const styledEdges = useMemo(() => { return edges.map(edge => ({ ...edge, style: { ...edge.style, strokeWidth: selectedEdgeId === edge.id ? 3 : 2, stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8', }, animated: selectedEdgeId === edge.id, })); }, [edges, selectedEdgeId]);
Complex node/edge state patterns with undo/redo and persistence.
typescripttype GraphAction = | { type: "SELECT_NODE"; payload: string } | { type: "SELECT_EDGE"; payload: string } | { type: "TOGGLE_EXPAND"; payload: string } | { type: "UPDATE_NODES"; payload: Node[] } | { type: "UPDATE_EDGES"; payload: Edge[] } | { type: "UNDO" } | { type: "REDO" }; const graphReducer = (state: GraphState, action: GraphAction): GraphState => { switch (action.type) { case "SELECT_NODE": return { ...state, selectedNodeId: action.payload, selectedEdgeId: null, }; case "TOGGLE_EXPAND": const newExpanded = new Set(state.expandedNodeIds); if (newExpanded.has(action.payload)) { newExpanded.delete(action.payload); } else { newExpanded.add(action.payload); } return { ...state, expandedNodeIds: newExpanded, isDirty: true, }; default: return state; } };
typescriptconst useHistoryManager = ( state: GraphState, dispatch: Dispatch<GraphAction>, ) => { const canUndo = state.historyIndex > 0; const canRedo = state.historyIndex < state.history.length - 1; const undo = useCallback(() => { if (canUndo) { const newIndex = state.historyIndex - 1; const historyEntry = state.history[newIndex]; dispatch({ type: "RESTORE_FROM_HISTORY", payload: { ...historyEntry, historyIndex: newIndex, }, }); } }, [canUndo, state.historyIndex, state.history]); const saveToHistory = useCallback(() => { dispatch({ type: "SAVE_TO_HISTORY" }); }, [dispatch]); return { canUndo, canRedo, undo, redo, saveToHistory }; };
Integrate Dagre for automatic graph layout:
typescriptimport dagre from "dagre"; const layoutOptions = { rankdir: "TB", // Top to Bottom nodesep: 100, // Node separation ranksep: 150, // Rank separation marginx: 50, marginy: 50, edgesep: 10, }; const applyLayout = (nodes: Node[], edges: Edge[]) => { const g = new dagre.graphlib.Graph(); g.setGraph(layoutOptions); g.setDefaultEdgeLabel(() => ({})); // Add nodes to graph nodes.forEach((node) => { g.setNode(node.id, { width: 200, height: 100 }); }); // Add edges to graph edges.forEach((edge) => { g.setEdge(edge.source, edge.target); }); // Calculate layout dagre.layout(g); // Apply positions return nodes.map((node) => ({ ...node, position: { x: g.node(node.id).x - 100, y: g.node(node.id).y - 50, }, })); }; // Debounce layout calculations const debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);
Isolate selected nodes and their direct connections:
typescriptconst useFocusMode = ( selectedNodeId: string, allNodes: Node[], allEdges: Edge[], ) => { return useMemo(() => { if (!selectedNodeId) return { nodes: allNodes, edges: allEdges }; // Get direct connections const connectedNodeIds = new Set([selectedNodeId]); const focusedEdges: Edge[] = []; allEdges.forEach((edge) => { if (edge.source === selectedNodeId || edge.target === selectedNodeId) { focusedEdges.push(edge); connectedNodeIds.add(edge.source); connectedNodeIds.add(edge.target); } }); // Get connected nodes const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id)); return { nodes: focusedNodes, edges: focusedEdges }; }, [selectedNodeId, allNodes, allEdges]); }; // Smooth transitions for focus mode const focusModeStyles = { transition: "all 0.3s ease-in-out", opacity: isInFocus ? 1 : 0.3, filter: isInFocus ? "none" : "blur(2px)", };
Search and navigate to specific nodes:
typescriptconst searchNodes = useCallback((nodes: Node[], query: string) => { if (!query.trim()) return []; const lowerQuery = query.toLowerCase(); return nodes.filter( (node) => node.data.label.toLowerCase().includes(lowerQuery) || node.data.description?.toLowerCase().includes(lowerQuery), ); }, []); const navigateToSearchResult = (nodeId: string) => { // Expand parent nodes const nodePath = calculateBreadcrumbPath(nodeId, allNodes); const parentIds = nodePath.slice(0, -1).map((n) => n.id); setExpandedIds((prev) => new Set([...prev, ...parentIds])); setSelectedNodeId(nodeId); // Fit view to node fitView({ nodes: [{ id: nodeId }], duration: 800 }); };
Create a performance analysis script:
javascript// scripts/graph-analyzer.js class GraphAnalyzer { analyzeCode(content, filePath) { const analysis = { metrics: { nodeCount: this.countNodes(content), edgeCount: this.countEdges(content), renderTime: this.estimateRenderTime(content), memoryUsage: this.estimateMemoryUsage(content), complexity: this.calculateComplexity(content), }, issues: [], optimizations: [], patterns: this.detectPatterns(content), }; // Detect performance issues this.detectPerformanceIssues(analysis); // Suggest optimizations this.suggestOptimizations(analysis); return analysis; } countNodes(content) { const nodePatterns = [ /nodes:\s*\[.*?\]/gs, /const\s+\w+\s*=\s*\[.*?id:.*?position:/gs, ]; let totalCount = 0; nodePatterns.forEach((pattern) => { const matches = content.match(pattern); if (matches) { matches.forEach((match) => { const nodeMatches = match.match(/id:\s*['"`][^'"`]+['"`]/g); if (nodeMatches) { totalCount += nodeMatches.length; } }); } }); return totalCount; } estimateRenderTime(content) { const nodeCount = this.countNodes(content); const edgeCount = this.countEdges(content); // Base render time estimation (ms) const baseTime = 5; const nodeTime = nodeCount * 0.1; const edgeTime = edgeCount * 0.05; return baseTime + nodeTime + edgeTime; } detectPerformanceIssues(analysis) { const { metrics } = analysis; if (metrics.nodeCount > 500) { analysis.issues.push({ type: "HIGH_NODE_COUNT", severity: "high", message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`, suggestion: "Implement virtualization or reduce visible nodes", }); } if (metrics.renderTime > 16) { analysis.issues.push({ type: "SLOW_RENDER", severity: "high", message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`, suggestion: "Optimize with memoization and incremental rendering", }); } } }
typescript// Use Map for O(1) lookups instead of array.find const nodesById = useMemo( () => new Map(allNodes.map((n) => [n.id, n])), [allNodes], ); // Cache layout results const layoutCacheRef = useRef<Map<string, Node[]>>(new Map()); // Proper cleanup in useEffect useEffect(() => { return () => { // Clean up any lingering references nodesMapRef.current.clear(); edgesMapRef.current.clear(); }; }, []);
typescript// Use useRef for objects that shouldn't trigger re-renders const autoSaveDataRef = useRef({ nodes: [], edges: [], lastSaved: Date.now(), }); // Update properties without breaking reference const updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => { autoSaveDataRef.current.nodes = newNodes; autoSaveDataRef.current.edges = newEdges; autoSaveDataRef.current.lastSaved = Date.now(); };
typescriptimport React, { useState, useCallback, useMemo, useRef } from 'react'; import ReactFlow, { Node, Edge, useReactFlow } from 'reactflow'; import dagre from 'dagre'; import { debounce } from 'lodash'; interface GraphState { nodes: Node[]; edges: Edge[]; selectedNodeId: string | null; expandedNodeIds: Set<string>; history: GraphState[]; historyIndex: number; } export default function InteractiveGraph() { const [state, setState] = useState<GraphState>({ nodes: [], edges: [], selectedNodeId: null, expandedNodeIds: new Set(), history: [], historyIndex: 0, }); const { fitView } = useReactFlow(); const layoutCacheRef = useRef<Map<string, Node[]>>(new Map()); // Memoized styled edges const styledEdges = useMemo(() => { return state.edges.map(edge => ({ ...edge, style: { ...edge.style, strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2, stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8', }, animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target, })); }, [state.edges, state.selectedNodeId]); // Debounced layout calculation const debouncedLayout = useMemo( () => debounce((nodes: Node[], edges: Edge[]) => { const cacheKey = generateLayoutCacheKey(nodes, edges); if (layoutCacheRef.current.has(cacheKey)) { return layoutCacheRef.current.get(cacheKey)!; } const layouted = applyDagreLayout(nodes, edges); layoutCacheRef.current.set(cacheKey, layouted); return layouted; }, 150), [] ); const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => { setState(prev => ({ ...prev, selectedNodeId: node.id, })); }, []); const handleToggleExpand = useCallback((nodeId: string) => { setState(prev => { const newExpanded = new Set(prev.expandedNodeIds); if (newExpanded.has(nodeId)) { newExpanded.delete(nodeId); } else { newExpanded.add(nodeId); } return { ...prev, expandedNodeIds: newExpanded, }; }); }, []); return ( <ReactFlow nodes={state.nodes} edges={styledEdges} onNodeClick={handleNodeClick} fitView /> ); }
This comprehensive skill provides everything needed to build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management patterns.
This skill is applicable to execute the workflow or actions described in the overview.
Other measured skills in the registry, with their headline benchmark lift.