Expert ReactFlow architect for building interactive graph applications with hierarchical node-edge systems, performance optimization, and auto-layout integration. Use when Claude needs to create or optimize ReactFlow applications for: (1) Interactive process graphs with expand/collapse navigation, (2) Hierarchical tree structures with drag & drop, (3) Performance-optimized large datasets with incremental rendering, (4) Auto-layout integration with Dagre, (5) Complex state management for nodes an
Add this skill
npx mdskills install sickn33/react-flow-architectComprehensive ReactFlow patterns with advanced hierarchical navigation and performance optimization techniques
1---2name: react-flow-architect3description: "Expert ReactFlow architect for building interactive graph applications with hierarchical node-edge systems, performance optimization, and auto-layout integration. Use when Claude needs to create or optimize ReactFlow applications for: (1) Interactive process graphs with expand/collapse navigation, (2) Hierarchical tree structures with drag & drop, (3) Performance-optimized large datasets with incremental rendering, (4) Auto-layout integration with Dagre, (5) Complex state management for nodes and edges, or any advanced ReactFlow visualization requirements."4---56# ReactFlow Architect78Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.910## Quick Start1112Create basic interactive graph:1314```tsx15import ReactFlow, { Node, Edge } from "reactflow";1617const nodes: Node[] = [18 { id: "1", position: { x: 0, y: 0 }, data: { label: "Node 1" } },19 { id: "2", position: { x: 100, y: 100 }, data: { label: "Node 2" } },20];2122const edges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }];2324export default function Graph() {25 return <ReactFlow nodes={nodes} edges={edges} />;26}27```2829## Core Patterns3031### Hierarchical Tree Navigation3233Build expandable/collapsible tree structures with parent-child relationships.3435#### Node Schema3637```typescript38interface TreeNode extends Node {39 data: {40 label: string;41 level: number;42 hasChildren: boolean;43 isExpanded: boolean;44 childCount: number;45 category: "root" | "category" | "process" | "detail";46 };47}48```4950#### Incremental Node Building5152```typescript53const buildVisibleNodes = useCallback(54 (allNodes: TreeNode[], expandedIds: Set<string>, otherDeps: any[]) => {55 const visibleNodes = new Map<string, TreeNode>();56 const visibleEdges = new Map<string, TreeEdge>();5758 // Start with root nodes59 const rootNodes = allNodes.filter((n) => n.data.level === 0);6061 // Recursively add visible nodes62 const addVisibleChildren = (node: TreeNode) => {63 visibleNodes.set(node.id, node);6465 if (expandedIds.has(node.id)) {66 const children = allNodes.filter((n) => n.parentNode === node.id);67 children.forEach((child) => addVisibleChildren(child));68 }69 };7071 rootNodes.forEach((root) => addVisibleChildren(root));7273 return {74 nodes: Array.from(visibleNodes.values()),75 edges: Array.from(visibleEdges.values()),76 };77 },78 [],79);80```8182### Performance Optimization8384Handle large datasets with incremental rendering and memoization.8586#### Incremental Rendering8788```typescript89const useIncrementalGraph = (90 allNodes: Node[],91 allEdges: Edge[],92 expandedList: string[],93) => {94 const prevExpandedListRef = useRef<Set<string>>(new Set());95 const prevOtherDepsRef = useRef<any[]>([]);9697 const { visibleNodes, visibleEdges } = useMemo(() => {98 const currentExpandedSet = new Set(expandedList);99 const prevExpandedSet = prevExpandedListRef.current;100101 // Check if expanded list changed102 const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet);103104 // Check if other dependencies changed105 const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current);106107 if (expandedChanged && !otherDepsChanged) {108 // Only expanded list changed - incremental update109 return buildIncrementalUpdate(110 cachedVisibleNodesRef.current,111 cachedVisibleEdgesRef.current,112 allNodes,113 allEdges,114 currentExpandedSet,115 prevExpandedSet,116 );117 } else {118 // Full rebuild needed119 return buildFullGraph(allNodes, allEdges, currentExpandedSet);120 }121 }, [allNodes, allEdges, expandedList, ...otherDeps]);122123 return { visibleNodes, visibleEdges };124};125```126127#### Memoization Patterns128129```typescript130// Memoize node components to prevent unnecessary re-renders131const ProcessNode = memo(({ data, selected }: NodeProps) => {132 return (133 <div className={`process-node ${selected ? 'selected' : ''}`}>134 {data.label}135 </div>136 );137}, (prevProps, nextProps) => {138 // Custom comparison function139 return (140 prevProps.data.label === nextProps.data.label &&141 prevProps.selected === nextProps.selected &&142 prevProps.data.isExpanded === nextProps.data.isExpanded143 );144});145146// Memoize edge calculations147const styledEdges = useMemo(() => {148 return edges.map(edge => ({149 ...edge,150 style: {151 ...edge.style,152 strokeWidth: selectedEdgeId === edge.id ? 3 : 2,153 stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8',154 },155 animated: selectedEdgeId === edge.id,156 }));157}, [edges, selectedEdgeId]);158```159160### State Management161162Complex node/edge state patterns with undo/redo and persistence.163164#### Reducer Pattern165166```typescript167type GraphAction =168 | { type: "SELECT_NODE"; payload: string }169 | { type: "SELECT_EDGE"; payload: string }170 | { type: "TOGGLE_EXPAND"; payload: string }171 | { type: "UPDATE_NODES"; payload: Node[] }172 | { type: "UPDATE_EDGES"; payload: Edge[] }173 | { type: "UNDO" }174 | { type: "REDO" };175176const graphReducer = (state: GraphState, action: GraphAction): GraphState => {177 switch (action.type) {178 case "SELECT_NODE":179 return {180 ...state,181 selectedNodeId: action.payload,182 selectedEdgeId: null,183 };184185 case "TOGGLE_EXPAND":186 const newExpanded = new Set(state.expandedNodeIds);187 if (newExpanded.has(action.payload)) {188 newExpanded.delete(action.payload);189 } else {190 newExpanded.add(action.payload);191 }192 return {193 ...state,194 expandedNodeIds: newExpanded,195 isDirty: true,196 };197198 default:199 return state;200 }201};202```203204#### History Management205206```typescript207const useHistoryManager = (208 state: GraphState,209 dispatch: Dispatch<GraphAction>,210) => {211 const canUndo = state.historyIndex > 0;212 const canRedo = state.historyIndex < state.history.length - 1;213214 const undo = useCallback(() => {215 if (canUndo) {216 const newIndex = state.historyIndex - 1;217 const historyEntry = state.history[newIndex];218219 dispatch({220 type: "RESTORE_FROM_HISTORY",221 payload: {222 ...historyEntry,223 historyIndex: newIndex,224 },225 });226 }227 }, [canUndo, state.historyIndex, state.history]);228229 const saveToHistory = useCallback(() => {230 dispatch({ type: "SAVE_TO_HISTORY" });231 }, [dispatch]);232233 return { canUndo, canRedo, undo, redo, saveToHistory };234};235```236237## Advanced Features238239### Auto-Layout Integration240241Integrate Dagre for automatic graph layout:242243```typescript244import dagre from "dagre";245246const layoutOptions = {247 rankdir: "TB", // Top to Bottom248 nodesep: 100, // Node separation249 ranksep: 150, // Rank separation250 marginx: 50,251 marginy: 50,252 edgesep: 10,253};254255const applyLayout = (nodes: Node[], edges: Edge[]) => {256 const g = new dagre.graphlib.Graph();257 g.setGraph(layoutOptions);258 g.setDefaultEdgeLabel(() => ({}));259260 // Add nodes to graph261 nodes.forEach((node) => {262 g.setNode(node.id, { width: 200, height: 100 });263 });264265 // Add edges to graph266 edges.forEach((edge) => {267 g.setEdge(edge.source, edge.target);268 });269270 // Calculate layout271 dagre.layout(g);272273 // Apply positions274 return nodes.map((node) => ({275 ...node,276 position: {277 x: g.node(node.id).x - 100,278 y: g.node(node.id).y - 50,279 },280 }));281};282283// Debounce layout calculations284const debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);285```286287### Focus Mode288289Isolate selected nodes and their direct connections:290291```typescript292const useFocusMode = (293 selectedNodeId: string,294 allNodes: Node[],295 allEdges: Edge[],296) => {297 return useMemo(() => {298 if (!selectedNodeId) return { nodes: allNodes, edges: allEdges };299300 // Get direct connections301 const connectedNodeIds = new Set([selectedNodeId]);302 const focusedEdges: Edge[] = [];303304 allEdges.forEach((edge) => {305 if (edge.source === selectedNodeId || edge.target === selectedNodeId) {306 focusedEdges.push(edge);307 connectedNodeIds.add(edge.source);308 connectedNodeIds.add(edge.target);309 }310 });311312 // Get connected nodes313 const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id));314315 return { nodes: focusedNodes, edges: focusedEdges };316 }, [selectedNodeId, allNodes, allEdges]);317};318319// Smooth transitions for focus mode320const focusModeStyles = {321 transition: "all 0.3s ease-in-out",322 opacity: isInFocus ? 1 : 0.3,323 filter: isInFocus ? "none" : "blur(2px)",324};325```326327### Search Integration328329Search and navigate to specific nodes:330331```typescript332const searchNodes = useCallback((nodes: Node[], query: string) => {333 if (!query.trim()) return [];334335 const lowerQuery = query.toLowerCase();336 return nodes.filter(337 (node) =>338 node.data.label.toLowerCase().includes(lowerQuery) ||339 node.data.description?.toLowerCase().includes(lowerQuery),340 );341}, []);342343const navigateToSearchResult = (nodeId: string) => {344 // Expand parent nodes345 const nodePath = calculateBreadcrumbPath(nodeId, allNodes);346 const parentIds = nodePath.slice(0, -1).map((n) => n.id);347348 setExpandedIds((prev) => new Set([...prev, ...parentIds]));349 setSelectedNodeId(nodeId);350351 // Fit view to node352 fitView({ nodes: [{ id: nodeId }], duration: 800 });353};354```355356## Performance Tools357358### Graph Performance Analyzer359360Create a performance analysis script:361362```javascript363// scripts/graph-analyzer.js364class GraphAnalyzer {365 analyzeCode(content, filePath) {366 const analysis = {367 metrics: {368 nodeCount: this.countNodes(content),369 edgeCount: this.countEdges(content),370 renderTime: this.estimateRenderTime(content),371 memoryUsage: this.estimateMemoryUsage(content),372 complexity: this.calculateComplexity(content),373 },374 issues: [],375 optimizations: [],376 patterns: this.detectPatterns(content),377 };378379 // Detect performance issues380 this.detectPerformanceIssues(analysis);381382 // Suggest optimizations383 this.suggestOptimizations(analysis);384385 return analysis;386 }387388 countNodes(content) {389 const nodePatterns = [390 /nodes:\s*\[.*?\]/gs,391 /const\s+\w+\s*=\s*\[.*?id:.*?position:/gs,392 ];393394 let totalCount = 0;395 nodePatterns.forEach((pattern) => {396 const matches = content.match(pattern);397 if (matches) {398 matches.forEach((match) => {399 const nodeMatches = match.match(/id:\s*['"`][^'"`]+['"`]/g);400 if (nodeMatches) {401 totalCount += nodeMatches.length;402 }403 });404 }405 });406407 return totalCount;408 }409410 estimateRenderTime(content) {411 const nodeCount = this.countNodes(content);412 const edgeCount = this.countEdges(content);413414 // Base render time estimation (ms)415 const baseTime = 5;416 const nodeTime = nodeCount * 0.1;417 const edgeTime = edgeCount * 0.05;418419 return baseTime + nodeTime + edgeTime;420 }421422 detectPerformanceIssues(analysis) {423 const { metrics } = analysis;424425 if (metrics.nodeCount > 500) {426 analysis.issues.push({427 type: "HIGH_NODE_COUNT",428 severity: "high",429 message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`,430 suggestion: "Implement virtualization or reduce visible nodes",431 });432 }433434 if (metrics.renderTime > 16) {435 analysis.issues.push({436 type: "SLOW_RENDER",437 severity: "high",438 message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`,439 suggestion: "Optimize with memoization and incremental rendering",440 });441 }442 }443}444```445446## Best Practices447448### Performance Guidelines4494501. **Use React.memo** for node components to prevent unnecessary re-renders4512. **Implement virtualization** for graphs with 1000+ nodes4523. **Debounce layout calculations** during rapid interactions4534. **Use useCallback** for edge creation and manipulation functions4545. **Implement proper TypeScript types** for nodes and edges455456### Memory Management457458```typescript459// Use Map for O(1) lookups instead of array.find460const nodesById = useMemo(461 () => new Map(allNodes.map((n) => [n.id, n])),462 [allNodes],463);464465// Cache layout results466const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());467468// Proper cleanup in useEffect469useEffect(() => {470 return () => {471 // Clean up any lingering references472 nodesMapRef.current.clear();473 edgesMapRef.current.clear();474 };475}, []);476```477478### State Optimization479480```typescript481// Use useRef for objects that shouldn't trigger re-renders482const autoSaveDataRef = useRef({483 nodes: [],484 edges: [],485 lastSaved: Date.now(),486});487488// Update properties without breaking reference489const updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {490 autoSaveDataRef.current.nodes = newNodes;491 autoSaveDataRef.current.edges = newEdges;492 autoSaveDataRef.current.lastSaved = Date.now();493};494```495496## Common Problems & Solutions497498### Performance Issues499500- **Problem**: Lag during node expansion501- **Solution**: Implement incremental rendering with change detection502503- **Problem**: Memory usage increases over time504- **Solution**: Proper cleanup in useEffect hooks and use WeakMap for temporary data505506### Layout Conflicts507508- **Problem**: Manual positioning conflicts with auto-layout509- **Solution**: Use controlled positioning state and separate layout modes510511### Rendering Issues512513- **Problem**: Excessive re-renders514- **Solution**: Use memo, useMemo, and useCallback with stable dependencies515516- **Problem**: Slow layout calculations517- **Solution**: Debounce layout calculations and cache results518519## Complete Example520521```typescript522import React, { useState, useCallback, useMemo, useRef } from 'react';523import ReactFlow, { Node, Edge, useReactFlow } from 'reactflow';524import dagre from 'dagre';525import { debounce } from 'lodash';526527interface GraphState {528 nodes: Node[];529 edges: Edge[];530 selectedNodeId: string | null;531 expandedNodeIds: Set<string>;532 history: GraphState[];533 historyIndex: number;534}535536export default function InteractiveGraph() {537 const [state, setState] = useState<GraphState>({538 nodes: [],539 edges: [],540 selectedNodeId: null,541 expandedNodeIds: new Set(),542 history: [],543 historyIndex: 0,544 });545546 const { fitView } = useReactFlow();547 const layoutCacheRef = useRef<Map<string, Node[]>>(new Map());548549 // Memoized styled edges550 const styledEdges = useMemo(() => {551 return state.edges.map(edge => ({552 ...edge,553 style: {554 ...edge.style,555 strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2,556 stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8',557 },558 animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target,559 }));560 }, [state.edges, state.selectedNodeId]);561562 // Debounced layout calculation563 const debouncedLayout = useMemo(564 () => debounce((nodes: Node[], edges: Edge[]) => {565 const cacheKey = generateLayoutCacheKey(nodes, edges);566567 if (layoutCacheRef.current.has(cacheKey)) {568 return layoutCacheRef.current.get(cacheKey)!;569 }570571 const layouted = applyDagreLayout(nodes, edges);572 layoutCacheRef.current.set(cacheKey, layouted);573574 return layouted;575 }, 150),576 []577 );578579 const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => {580 setState(prev => ({581 ...prev,582 selectedNodeId: node.id,583 }));584 }, []);585586 const handleToggleExpand = useCallback((nodeId: string) => {587 setState(prev => {588 const newExpanded = new Set(prev.expandedNodeIds);589 if (newExpanded.has(nodeId)) {590 newExpanded.delete(nodeId);591 } else {592 newExpanded.add(nodeId);593 }594595 return {596 ...prev,597 expandedNodeIds: newExpanded,598 };599 });600 }, []);601602 return (603 <ReactFlow604 nodes={state.nodes}605 edges={styledEdges}606 onNodeClick={handleNodeClick}607 fitView608 />609 );610}611```612613This comprehensive skill provides everything needed to build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management patterns.614
Full transparency — inspect the skill content before installing.