[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M714D1JM2BBC5GSJ2PFWPQJP":3,"skill-same:01M714D1JM2BBC5GSJ2PFWPQJP":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":14,"repositoryHtmlUrl":53,"repositoryHomepageUrl":6,"ownerHtmlUrl":54,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":55,"install":56,"resources":69},"01M714D1JM2BBC5GSJ2PFWPQJP","react-flow-architect",null,"Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.","sapsapshen\u002Fai-memory-router","sapsapshen","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F20347959?v=4","01M6XDQEF1DP50DKF37H81J3J5","shenmian","01M6XDQEJPE41Y3DT2763X8ZJN",0,"MIT","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcopilot\u002Freact-flow-architect\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"react","flow","architect","react flow architect","react flow","2026","performance","unknown","production-ready","advanced","with","community","build","ready","performance optimization","applications","Production","Optimization","state","state management","2026-11-10 10:12:30",false,"---\nname: react-flow-architect\ndescription: \"Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# ReactFlow Architect\n\nBuild production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.\n\n## Quick Start\n\nCreate basic interactive graph:\n\n```tsx\nimport ReactFlow, { Node, Edge } from \"reactflow\";\n\nconst nodes: Node[] = [\n  { id: \"1\", position: { x: 0, y: 0 }, data: { label: \"Node 1\" } },\n  { id: \"2\", position: { x: 100, y: 100 }, data: { label: \"Node 2\" } },\n];\n\nconst edges: Edge[] = [{ id: \"e1-2\", source: \"1\", target: \"2\" }];\n\nexport default function Graph() {\n  return \u003CReactFlow nodes={nodes} edges={edges} \u002F>;\n}\n```\n\n## Core Patterns\n\n### Hierarchical Tree Navigation\n\nBuild expandable\u002Fcollapsible tree structures with parent-child relationships.\n\n#### Node Schema\n\n```typescript\ninterface TreeNode extends Node {\n  data: {\n    label: string;\n    level: number;\n    hasChildren: boolean;\n    isExpanded: boolean;\n    childCount: number;\n    category: \"root\" | \"category\" | \"process\" | \"detail\";\n  };\n}\n```\n\n#### Incremental Node Building\n\n```typescript\nconst buildVisibleNodes = useCallback(\n  (allNodes: TreeNode[], expandedIds: Set\u003Cstring>, otherDeps: any[]) => {\n    const visibleNodes = new Map\u003Cstring, TreeNode>();\n    const visibleEdges = new Map\u003Cstring, TreeEdge>();\n\n    \u002F\u002F Start with root nodes\n    const rootNodes = allNodes.filter((n) => n.data.level === 0);\n\n    \u002F\u002F Recursively add visible nodes\n    const addVisibleChildren = (node: TreeNode) => {\n      visibleNodes.set(node.id, node);\n\n      if (expandedIds.has(node.id)) {\n        const children = allNodes.filter((n) => n.parentNode === node.id);\n        children.forEach((child) => addVisibleChildren(child));\n      }\n    };\n\n    rootNodes.forEach((root) => addVisibleChildren(root));\n\n    return {\n      nodes: Array.from(visibleNodes.values()),\n      edges: Array.from(visibleEdges.values()),\n    };\n  },\n  [],\n);\n```\n\n### Performance Optimization\n\nHandle large datasets with incremental rendering and memoization.\n\n#### Incremental Rendering\n\n```typescript\nconst useIncrementalGraph = (\n  allNodes: Node[],\n  allEdges: Edge[],\n  expandedList: string[],\n) => {\n  const prevExpandedListRef = useRef\u003CSet\u003Cstring>>(new Set());\n  const prevOtherDepsRef = useRef\u003Cany[]>([]);\n\n  const { visibleNodes, visibleEdges } = useMemo(() => {\n    const currentExpandedSet = new Set(expandedList);\n    const prevExpandedSet = prevExpandedListRef.current;\n\n    \u002F\u002F Check if expanded list changed\n    const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet);\n\n    \u002F\u002F Check if other dependencies changed\n    const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current);\n\n    if (expandedChanged && !otherDepsChanged) {\n      \u002F\u002F Only expanded list changed - incremental update\n      return buildIncrementalUpdate(\n        cachedVisibleNodesRef.current,\n        cachedVisibleEdgesRef.current,\n        allNodes,\n        allEdges,\n        currentExpandedSet,\n        prevExpandedSet,\n      );\n    } else {\n      \u002F\u002F Full rebuild needed\n      return buildFullGraph(allNodes, allEdges, currentExpandedSet);\n    }\n  }, [allNodes, allEdges, expandedList, ...otherDeps]);\n\n  return { visibleNodes, visibleEdges };\n};\n```\n\n#### Memoization Patterns\n\n```typescript\n\u002F\u002F Memoize node components to prevent unnecessary re-renders\nconst ProcessNode = memo(({ data, selected }: NodeProps) => {\n  return (\n    \u003Cdiv className={`process-node ${selected ? 'selected' : ''}`}>\n      {data.label}\n    \u003C\u002Fdiv>\n  );\n}, (prevProps, nextProps) => {\n  \u002F\u002F Custom comparison function\n  return (\n    prevProps.data.label === nextProps.data.label &&\n    prevProps.selected === nextProps.selected &&\n    prevProps.data.isExpanded === nextProps.data.isExpanded\n  );\n});\n\n\u002F\u002F Memoize edge calculations\nconst styledEdges = useMemo(() => {\n  return edges.map(edge => ({\n    ...edge,\n    style: {\n      ...edge.style,\n      strokeWidth: selectedEdgeId === edge.id ? 3 : 2,\n      stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8',\n    },\n    animated: selectedEdgeId === edge.id,\n  }));\n}, [edges, selectedEdgeId]);\n```\n\n### State Management\n\nComplex node\u002Fedge state patterns with undo\u002Fredo and persistence.\n\n#### Reducer Pattern\n\n```typescript\ntype GraphAction =\n  | { type: \"SELECT_NODE\"; payload: string }\n  | { type: \"SELECT_EDGE\"; payload: string }\n  | { type: \"TOGGLE_EXPAND\"; payload: string }\n  | { type: \"UPDATE_NODES\"; payload: Node[] }\n  | { type: \"UPDATE_EDGES\"; payload: Edge[] }\n  | { type: \"UNDO\" }\n  | { type: \"REDO\" };\n\nconst graphReducer = (state: GraphState, action: GraphAction): GraphState => {\n  switch (action.type) {\n    case \"SELECT_NODE\":\n      return {\n        ...state,\n        selectedNodeId: action.payload,\n        selectedEdgeId: null,\n      };\n\n    case \"TOGGLE_EXPAND\":\n      const newExpanded = new Set(state.expandedNodeIds);\n      if (newExpanded.has(action.payload)) {\n        newExpanded.delete(action.payload);\n      } else {\n        newExpanded.add(action.payload);\n      }\n      return {\n        ...state,\n        expandedNodeIds: newExpanded,\n        isDirty: true,\n      };\n\n    default:\n      return state;\n  }\n};\n```\n\n#### History Management\n\n```typescript\nconst useHistoryManager = (\n  state: GraphState,\n  dispatch: Dispatch\u003CGraphAction>,\n) => {\n  const canUndo = state.historyIndex > 0;\n  const canRedo = state.historyIndex \u003C state.history.length - 1;\n\n  const undo = useCallback(() => {\n    if (canUndo) {\n      const newIndex = state.historyIndex - 1;\n      const historyEntry = state.history[newIndex];\n\n      dispatch({\n        type: \"RESTORE_FROM_HISTORY\",\n        payload: {\n          ...historyEntry,\n          historyIndex: newIndex,\n        },\n      });\n    }\n  }, [canUndo, state.historyIndex, state.history]);\n\n  const saveToHistory = useCallback(() => {\n    dispatch({ type: \"SAVE_TO_HISTORY\" });\n  }, [dispatch]);\n\n  return { canUndo, canRedo, undo, redo, saveToHistory };\n};\n```\n\n## Advanced Features\n\n### Auto-Layout Integration\n\nIntegrate Dagre for automatic graph layout:\n\n```typescript\nimport dagre from \"dagre\";\n\nconst layoutOptions = {\n  rankdir: \"TB\", \u002F\u002F Top to Bottom\n  nodesep: 100, \u002F\u002F Node separation\n  ranksep: 150, \u002F\u002F Rank separation\n  marginx: 50,\n  marginy: 50,\n  edgesep: 10,\n};\n\nconst applyLayout = (nodes: Node[], edges: Edge[]) => {\n  const g = new dagre.graphlib.Graph();\n  g.setGraph(layoutOptions);\n  g.setDefaultEdgeLabel(() => ({}));\n\n  \u002F\u002F Add nodes to graph\n  nodes.forEach((node) => {\n    g.setNode(node.id, { width: 200, height: 100 });\n  });\n\n  \u002F\u002F Add edges to graph\n  edges.forEach((edge) => {\n    g.setEdge(edge.source, edge.target);\n  });\n\n  \u002F\u002F Calculate layout\n  dagre.layout(g);\n\n  \u002F\u002F Apply positions\n  return nodes.map((node) => ({\n    ...node,\n    position: {\n      x: g.node(node.id).x - 100,\n      y: g.node(node.id).y - 50,\n    },\n  }));\n};\n\n\u002F\u002F Debounce layout calculations\nconst debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);\n```\n\n### Focus Mode\n\nIsolate selected nodes and their direct connections:\n\n```typescript\nconst useFocusMode = (\n  selectedNodeId: string,\n  allNodes: Node[],\n  allEdges: Edge[],\n) => {\n  return useMemo(() => {\n    if (!selectedNodeId) return { nodes: allNodes, edges: allEdges };\n\n    \u002F\u002F Get direct connections\n    const connectedNodeIds = new Set([selectedNodeId]);\n    const focusedEdges: Edge[] = [];\n\n    allEdges.forEach((edge) => {\n      if (edge.source === selectedNodeId || edge.target === selectedNodeId) {\n        focusedEdges.push(edge);\n        connectedNodeIds.add(edge.source);\n        connectedNodeIds.add(edge.target);\n      }\n    });\n\n    \u002F\u002F Get connected nodes\n    const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id));\n\n    return { nodes: focusedNodes, edges: focusedEdges };\n  }, [selectedNodeId, allNodes, allEdges]);\n};\n\n\u002F\u002F Smooth transitions for focus mode\nconst focusModeStyles = {\n  transition: \"all 0.3s ease-in-out\",\n  opacity: isInFocus ? 1 : 0.3,\n  filter: isInFocus ? \"none\" : \"blur(2px)\",\n};\n```\n\n### Search Integration\n\nSearch and navigate to specific nodes:\n\n```typescript\nconst searchNodes = useCallback((nodes: Node[], query: string) => {\n  if (!query.trim()) return [];\n\n  const lowerQuery = query.toLowerCase();\n  return nodes.filter(\n    (node) =>\n      node.data.label.toLowerCase().includes(lowerQuery) ||\n      node.data.description?.toLowerCase().includes(lowerQuery),\n  );\n}, []);\n\nconst navigateToSearchResult = (nodeId: string) => {\n  \u002F\u002F Expand parent nodes\n  const nodePath = calculateBreadcrumbPath(nodeId, allNodes);\n  const parentIds = nodePath.slice(0, -1).map((n) => n.id);\n\n  setExpandedIds((prev) => new Set([...prev, ...parentIds]));\n  setSelectedNodeId(nodeId);\n\n  \u002F\u002F Fit view to node\n  fitView({ nodes: [{ id: nodeId }], duration: 800 });\n};\n```\n\n## Performance Tools\n\n### Graph Performance Analyzer\n\nCreate a performance analysis script:\n\n```javascript\n\u002F\u002F scripts\u002Fgraph-analyzer.js\nclass GraphAnalyzer {\n  analyzeCode(content, filePath) {\n    const analysis = {\n      metrics: {\n        nodeCount: this.countNodes(content),\n        edgeCount: this.countEdges(content),\n        renderTime: this.estimateRenderTime(content),\n        memoryUsage: this.estimateMemoryUsage(content),\n        complexity: this.calculateComplexity(content),\n      },\n      issues: [],\n      optimizations: [],\n      patterns: this.detectPatterns(content),\n    };\n\n    \u002F\u002F Detect performance issues\n    this.detectPerformanceIssues(analysis);\n\n    \u002F\u002F Suggest optimizations\n    this.suggestOptimizations(analysis);\n\n    return analysis;\n  }\n\n  countNodes(content) {\n    const nodePatterns = [\n      \u002Fnodes:\\s*\\[.*?\\]\u002Fgs,\n      \u002Fconst\\s+\\w+\\s*=\\s*\\[.*?id:.*?position:\u002Fgs,\n    ];\n\n    let totalCount = 0;\n    nodePatterns.forEach((pattern) => {\n      const matches = content.match(pattern);\n      if (matches) {\n        matches.forEach((match) => {\n          const nodeMatches = match.match(\u002Fid:\\s*['\"`][^'\"`]+['\"`]\u002Fg);\n          if (nodeMatches) {\n            totalCount += nodeMatches.length;\n          }\n        });\n      }\n    });\n\n    return totalCount;\n  }\n\n  estimateRenderTime(content) {\n    const nodeCount = this.countNodes(content);\n    const edgeCount = this.countEdges(content);\n\n    \u002F\u002F Base render time estimation (ms)\n    const baseTime = 5;\n    const nodeTime = nodeCount * 0.1;\n    const edgeTime = edgeCount * 0.05;\n\n    return baseTime + nodeTime + edgeTime;\n  }\n\n  detectPerformanceIssues(analysis) {\n    const { metrics } = analysis;\n\n    if (metrics.nodeCount > 500) {\n      analysis.issues.push({\n        type: \"HIGH_NODE_COUNT\",\n        severity: \"high\",\n        message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`,\n        suggestion: \"Implement virtualization or reduce visible nodes\",\n      });\n    }\n\n    if (metrics.renderTime > 16) {\n      analysis.issues.push({\n        type: \"SLOW_RENDER\",\n        severity: \"high\",\n        message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`,\n        suggestion: \"Optimize with memoization and incremental rendering\",\n      });\n    }\n  }\n}\n```\n\n## Best Practices\n\n### Performance Guidelines\n\n1. **Use React.memo** for node components to prevent unnecessary re-renders\n2. **Implement virtualization** for graphs with 1000+ nodes\n3. **Debounce layout calculations** during rapid interactions\n4. **Use useCallback** for edge creation and manipulation functions\n5. **Implement proper TypeScript types** for nodes and edges\n\n### Memory Management\n\n```typescript\n\u002F\u002F Use Map for O(1) lookups instead of array.find\nconst nodesById = useMemo(\n  () => new Map(allNodes.map((n) => [n.id, n])),\n  [allNodes],\n);\n\n\u002F\u002F Cache layout results\nconst layoutCacheRef = useRef\u003CMap\u003Cstring, Node[]>>(new Map());\n\n\u002F\u002F Proper cleanup in useEffect\nuseEffect(() => {\n  return () => {\n    \u002F\u002F Clean up any lingering references\n    nodesMapRef.current.clear();\n    edgesMapRef.current.clear();\n  };\n}, []);\n```\n\n### State Optimization\n\n```typescript\n\u002F\u002F Use useRef for objects that shouldn't trigger re-renders\nconst autoSaveDataRef = useRef({\n  nodes: [],\n  edges: [],\n  lastSaved: Date.now(),\n});\n\n\u002F\u002F Update properties without breaking reference\nconst updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {\n  autoSaveDataRef.current.nodes = newNodes;\n  autoSaveDataRef.current.edges = newEdges;\n  autoSaveDataRef.current.lastSaved = Date.now();\n};\n```\n\n## Common Problems & Solutions\n\n### Performance Issues\n\n- **Problem**: Lag during node expansion\n- **Solution**: Implement incremental rendering with change detection\n\n- **Problem**: Memory usage increases over time\n- **Solution**: Proper cleanup in useEffect hooks and use WeakMap for temporary data\n\n### Layout Conflicts\n\n- **Problem**: Manual positioning conflicts with auto-layout\n- **Solution**: Use controlled positioning state and separate layout modes\n\n### Rendering Issues\n\n- **Problem**: Excessive re-renders\n- **Solution**: Use memo, useMemo, and useCallback with stable dependencies\n\n- **Problem**: Slow layout calculations\n- **Solution**: Debounce layout calculations and cache results\n\n## Complete Example\n\n```typescript\nimport React, { useState, useCallback, useMemo, useRef } from 'react';\nimport ReactFlow, { Node, Edge, useReactFlow } from 'reactflow';\nimport dagre from 'dagre';\nimport { debounce } from 'lodash';\n\ninterface GraphState {\n  nodes: Node[];\n  edges: Edge[];\n  selectedNodeId: string | null;\n  expandedNodeIds: Set\u003Cstring>;\n  history: GraphState[];\n  historyIndex: number;\n}\n\nexport default function InteractiveGraph() {\n  const [state, setState] = useState\u003CGraphState>({\n    nodes: [],\n    edges: [],\n    selectedNodeId: null,\n    expandedNodeIds: new Set(),\n    history: [],\n    historyIndex: 0,\n  });\n\n  const { fitView } = useReactFlow();\n  const layoutCacheRef = useRef\u003CMap\u003Cstring, Node[]>>(new Map());\n\n  \u002F\u002F Memoized styled edges\n  const styledEdges = useMemo(() => {\n    return state.edges.map(edge => ({\n      ...edge,\n      style: {\n        ...edge.style,\n        strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2,\n        stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8',\n      },\n      animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target,\n    }));\n  }, [state.edges, state.selectedNodeId]);\n\n  \u002F\u002F Debounced layout calculation\n  const debouncedLayout = useMemo(\n    () => debounce((nodes: Node[], edges: Edge[]) => {\n      const cacheKey = generateLayoutCacheKey(nodes, edges);\n\n      if (layoutCacheRef.current.has(cacheKey)) {\n        return layoutCacheRef.current.get(cacheKey)!;\n      }\n\n      const layouted = applyDagreLayout(nodes, edges);\n      layoutCacheRef.current.set(cacheKey, layouted);\n\n      return layouted;\n    }, 150),\n    []\n  );\n\n  const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => {\n    setState(prev => ({\n      ...prev,\n      selectedNodeId: node.id,\n    }));\n  }, []);\n\n  const handleToggleExpand = useCallback((nodeId: string) => {\n    setState(prev => {\n      const newExpanded = new Set(prev.expandedNodeIds);\n      if (newExpanded.has(nodeId)) {\n        newExpanded.delete(nodeId);\n      } else {\n        newExpanded.add(nodeId);\n      }\n\n      return {\n        ...prev,\n        expandedNodeIds: newExpanded,\n      };\n    });\n  }, []);\n\n  return (\n    \u003CReactFlow\n      nodes={state.nodes}\n      edges={styledEdges}\n      onNodeClick={handleNodeClick}\n      fitView\n    \u002F>\n  );\n}\n```\n\nThis comprehensive skill provides everything needed to build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management patterns.\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n","# ReactFlow Architect\n\nBuild production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.\n\n## Quick Start\n\nCreate basic interactive graph:\n\n```tsx\nimport ReactFlow, { Node, Edge } from \"reactflow\";\n\nconst nodes: Node[] = [\n  { id: \"1\", position: { x: 0, y: 0 }, data: { label: \"Node 1\" } },\n  { id: \"2\", position: { x: 100, y: 100 }, data: { label: \"Node 2\" } },\n];\n\nconst edges: Edge[] = [{ id: \"e1-2\", source: \"1\", target: \"2\" }];\n\nexport default function Graph() {\n  return \u003CReactFlow nodes={nodes} edges={edges} \u002F>;\n}\n```\n\n## Core Patterns\n\n### Hierarchical Tree Navigation\n\nBuild expandable\u002Fcollapsible tree structures with parent-child relationships.\n\n#### Node Schema\n\n```typescript\ninterface TreeNode extends Node {\n  data: {\n    label: string;\n    level: number;\n    hasChildren: boolean;\n    isExpanded: boolean;\n    childCount: number;\n    category: \"root\" | \"category\" | \"process\" | \"detail\";\n  };\n}\n```\n\n#### Incremental Node Building\n\n```typescript\nconst buildVisibleNodes = useCallback(\n  (allNodes: TreeNode[], expandedIds: Set\u003Cstring>, otherDeps: any[]) => {\n    const visibleNodes = new Map\u003Cstring, TreeNode>();\n    const visibleEdges = new Map\u003Cstring, TreeEdge>();\n\n    \u002F\u002F Start with root nodes\n    const rootNodes = allNodes.filter((n) => n.data.level === 0);\n\n    \u002F\u002F Recursively add visible nodes\n    const addVisibleChildren = (node: TreeNode) => {\n      visibleNodes.set(node.id, node);\n\n      if (expandedIds.has(node.id)) {\n        const children = allNodes.filter((n) => n.parentNode === node.id);\n        children.forEach((child) => addVisibleChildren(child));\n      }\n    };\n\n    rootNodes.forEach((root) => addVisibleChildren(root));\n\n    return {\n      nodes: Array.from(visibleNodes.values()),\n      edges: Array.from(visibleEdges.values()),\n    };\n  },\n  [],\n);\n```\n\n### Performance Optimization\n\nHandle large datasets with incremental rendering and memoization.\n\n#### Incremental Rendering\n\n```typescript\nconst useIncrementalGraph = (\n  allNodes: Node[],\n  allEdges: Edge[],\n  expandedList: string[],\n) => {\n  const prevExpandedListRef = useRef\u003CSet\u003Cstring>>(new Set());\n  const prevOtherDepsRef = useRef\u003Cany[]>([]);\n\n  const { visibleNodes, visibleEdges } = useMemo(() => {\n    const currentExpandedSet = new Set(expandedList);\n    const prevExpandedSet = prevExpandedListRef.current;\n\n    \u002F\u002F Check if expanded list changed\n    const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet);\n\n    \u002F\u002F Check if other dependencies changed\n    const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current);\n\n    if (expandedChanged && !otherDepsChanged) {\n      \u002F\u002F Only expanded list changed - incremental update\n      return buildIncrementalUpdate(\n        cachedVisibleNodesRef.current,\n        cachedVisibleEdgesRef.current,\n        allNodes,\n        allEdges,\n        currentExpandedSet,\n        prevExpandedSet,\n      );\n    } else {\n      \u002F\u002F Full rebuild needed\n      return buildFullGraph(allNodes, allEdges, currentExpandedSet);\n    }\n  }, [allNodes, allEdges, expandedList, ...otherDeps]);\n\n  return { visibleNodes, visibleEdges };\n};\n```\n\n#### Memoization Patterns\n\n```typescript\n\u002F\u002F Memoize node components to prevent unnecessary re-renders\nconst ProcessNode = memo(({ data, selected }: NodeProps) => {\n  return (\n    \u003Cdiv className={`process-node ${selected ? 'selected' : ''}`}>\n      {data.label}\n    \u003C\u002Fdiv>\n  );\n}, (prevProps, nextProps) => {\n  \u002F\u002F Custom comparison function\n  return (\n    prevProps.data.label === nextProps.data.label &&\n    prevProps.selected === nextProps.selected &&\n    prevProps.data.isExpanded === nextProps.data.isExpanded\n  );\n});\n\n\u002F\u002F Memoize edge calculations\nconst styledEdges = useMemo(() => {\n  return edges.map(edge => ({\n    ...edge,\n    style: {\n      ...edge.style,\n      strokeWidth: selectedEdgeId === edge.id ? 3 : 2,\n      stroke: selectedEdgeId === edge.id ? '#3b82f6' : '#94a3b8',\n    },\n    animated: selectedEdgeId === edge.id,\n  }));\n}, [edges, selectedEdgeId]);\n```\n\n### State Management\n\nComplex node\u002Fedge state patterns with undo\u002Fredo and persistence.\n\n#### Reducer Pattern\n\n```typescript\ntype GraphAction =\n  | { type: \"SELECT_NODE\"; payload: string }\n  | { type: \"SELECT_EDGE\"; payload: string }\n  | { type: \"TOGGLE_EXPAND\"; payload: string }\n  | { type: \"UPDATE_NODES\"; payload: Node[] }\n  | { type: \"UPDATE_EDGES\"; payload: Edge[] }\n  | { type: \"UNDO\" }\n  | { type: \"REDO\" };\n\nconst graphReducer = (state: GraphState, action: GraphAction): GraphState => {\n  switch (action.type) {\n    case \"SELECT_NODE\":\n      return {\n        ...state,\n        selectedNodeId: action.payload,\n        selectedEdgeId: null,\n      };\n\n    case \"TOGGLE_EXPAND\":\n      const newExpanded = new Set(state.expandedNodeIds);\n      if (newExpanded.has(action.payload)) {\n        newExpanded.delete(action.payload);\n      } else {\n        newExpanded.add(action.payload);\n      }\n      return {\n        ...state,\n        expandedNodeIds: newExpanded,\n        isDirty: true,\n      };\n\n    default:\n      return state;\n  }\n};\n```\n\n#### History Management\n\n```typescript\nconst useHistoryManager = (\n  state: GraphState,\n  dispatch: Dispatch\u003CGraphAction>,\n) => {\n  const canUndo = state.historyIndex > 0;\n  const canRedo = state.historyIndex \u003C state.history.length - 1;\n\n  const undo = useCallback(() => {\n    if (canUndo) {\n      const newIndex = state.historyIndex - 1;\n      const historyEntry = state.history[newIndex];\n\n      dispatch({\n        type: \"RESTORE_FROM_HISTORY\",\n        payload: {\n          ...historyEntry,\n          historyIndex: newIndex,\n        },\n      });\n    }\n  }, [canUndo, state.historyIndex, state.history]);\n\n  const saveToHistory = useCallback(() => {\n    dispatch({ type: \"SAVE_TO_HISTORY\" });\n  }, [dispatch]);\n\n  return { canUndo, canRedo, undo, redo, saveToHistory };\n};\n```\n\n## Advanced Features\n\n### Auto-Layout Integration\n\nIntegrate Dagre for automatic graph layout:\n\n```typescript\nimport dagre from \"dagre\";\n\nconst layoutOptions = {\n  rankdir: \"TB\", \u002F\u002F Top to Bottom\n  nodesep: 100, \u002F\u002F Node separation\n  ranksep: 150, \u002F\u002F Rank separation\n  marginx: 50,\n  marginy: 50,\n  edgesep: 10,\n};\n\nconst applyLayout = (nodes: Node[], edges: Edge[]) => {\n  const g = new dagre.graphlib.Graph();\n  g.setGraph(layoutOptions);\n  g.setDefaultEdgeLabel(() => ({}));\n\n  \u002F\u002F Add nodes to graph\n  nodes.forEach((node) => {\n    g.setNode(node.id, { width: 200, height: 100 });\n  });\n\n  \u002F\u002F Add edges to graph\n  edges.forEach((edge) => {\n    g.setEdge(edge.source, edge.target);\n  });\n\n  \u002F\u002F Calculate layout\n  dagre.layout(g);\n\n  \u002F\u002F Apply positions\n  return nodes.map((node) => ({\n    ...node,\n    position: {\n      x: g.node(node.id).x - 100,\n      y: g.node(node.id).y - 50,\n    },\n  }));\n};\n\n\u002F\u002F Debounce layout calculations\nconst debouncedLayout = useMemo(() => debounce(applyLayout, 150), []);\n```\n\n### Focus Mode\n\nIsolate selected nodes and their direct connections:\n\n```typescript\nconst useFocusMode = (\n  selectedNodeId: string,\n  allNodes: Node[],\n  allEdges: Edge[],\n) => {\n  return useMemo(() => {\n    if (!selectedNodeId) return { nodes: allNodes, edges: allEdges };\n\n    \u002F\u002F Get direct connections\n    const connectedNodeIds = new Set([selectedNodeId]);\n    const focusedEdges: Edge[] = [];\n\n    allEdges.forEach((edge) => {\n      if (edge.source === selectedNodeId || edge.target === selectedNodeId) {\n        focusedEdges.push(edge);\n        connectedNodeIds.add(edge.source);\n        connectedNodeIds.add(edge.target);\n      }\n    });\n\n    \u002F\u002F Get connected nodes\n    const focusedNodes = allNodes.filter((n) => connectedNodeIds.has(n.id));\n\n    return { nodes: focusedNodes, edges: focusedEdges };\n  }, [selectedNodeId, allNodes, allEdges]);\n};\n\n\u002F\u002F Smooth transitions for focus mode\nconst focusModeStyles = {\n  transition: \"all 0.3s ease-in-out\",\n  opacity: isInFocus ? 1 : 0.3,\n  filter: isInFocus ? \"none\" : \"blur(2px)\",\n};\n```\n\n### Search Integration\n\nSearch and navigate to specific nodes:\n\n```typescript\nconst searchNodes = useCallback((nodes: Node[], query: string) => {\n  if (!query.trim()) return [];\n\n  const lowerQuery = query.toLowerCase();\n  return nodes.filter(\n    (node) =>\n      node.data.label.toLowerCase().includes(lowerQuery) ||\n      node.data.description?.toLowerCase().includes(lowerQuery),\n  );\n}, []);\n\nconst navigateToSearchResult = (nodeId: string) => {\n  \u002F\u002F Expand parent nodes\n  const nodePath = calculateBreadcrumbPath(nodeId, allNodes);\n  const parentIds = nodePath.slice(0, -1).map((n) => n.id);\n\n  setExpandedIds((prev) => new Set([...prev, ...parentIds]));\n  setSelectedNodeId(nodeId);\n\n  \u002F\u002F Fit view to node\n  fitView({ nodes: [{ id: nodeId }], duration: 800 });\n};\n```\n\n## Performance Tools\n\n### Graph Performance Analyzer\n\nCreate a performance analysis script:\n\n```javascript\n\u002F\u002F scripts\u002Fgraph-analyzer.js\nclass GraphAnalyzer {\n  analyzeCode(content, filePath) {\n    const analysis = {\n      metrics: {\n        nodeCount: this.countNodes(content),\n        edgeCount: this.countEdges(content),\n        renderTime: this.estimateRenderTime(content),\n        memoryUsage: this.estimateMemoryUsage(content),\n        complexity: this.calculateComplexity(content),\n      },\n      issues: [],\n      optimizations: [],\n      patterns: this.detectPatterns(content),\n    };\n\n    \u002F\u002F Detect performance issues\n    this.detectPerformanceIssues(analysis);\n\n    \u002F\u002F Suggest optimizations\n    this.suggestOptimizations(analysis);\n\n    return analysis;\n  }\n\n  countNodes(content) {\n    const nodePatterns = [\n      \u002Fnodes:\\s*\\[.*?\\]\u002Fgs,\n      \u002Fconst\\s+\\w+\\s*=\\s*\\[.*?id:.*?position:\u002Fgs,\n    ];\n\n    let totalCount = 0;\n    nodePatterns.forEach((pattern) => {\n      const matches = content.match(pattern);\n      if (matches) {\n        matches.forEach((match) => {\n          const nodeMatches = match.match(\u002Fid:\\s*['\"`][^'\"`]+['\"`]\u002Fg);\n          if (nodeMatches) {\n            totalCount += nodeMatches.length;\n          }\n        });\n      }\n    });\n\n    return totalCount;\n  }\n\n  estimateRenderTime(content) {\n    const nodeCount = this.countNodes(content);\n    const edgeCount = this.countEdges(content);\n\n    \u002F\u002F Base render time estimation (ms)\n    const baseTime = 5;\n    const nodeTime = nodeCount * 0.1;\n    const edgeTime = edgeCount * 0.05;\n\n    return baseTime + nodeTime + edgeTime;\n  }\n\n  detectPerformanceIssues(analysis) {\n    const { metrics } = analysis;\n\n    if (metrics.nodeCount > 500) {\n      analysis.issues.push({\n        type: \"HIGH_NODE_COUNT\",\n        severity: \"high\",\n        message: `Too many nodes (${metrics.nodeCount}). Consider virtualization.`,\n        suggestion: \"Implement virtualization or reduce visible nodes\",\n      });\n    }\n\n    if (metrics.renderTime > 16) {\n      analysis.issues.push({\n        type: \"SLOW_RENDER\",\n        severity: \"high\",\n        message: `Render time (${metrics.renderTime.toFixed(2)}ms) exceeds 60fps.`,\n        suggestion: \"Optimize with memoization and incremental rendering\",\n      });\n    }\n  }\n}\n```\n\n## Best Practices\n\n### Performance Guidelines\n\n1. **Use React.memo** for node components to prevent unnecessary re-renders\n2. **Implement virtualization** for graphs with 1000+ nodes\n3. **Debounce layout calculations** during rapid interactions\n4. **Use useCallback** for edge creation and manipulation functions\n5. **Implement proper TypeScript types** for nodes and edges\n\n### Memory Management\n\n```typescript\n\u002F\u002F Use Map for O(1) lookups instead of array.find\nconst nodesById = useMemo(\n  () => new Map(allNodes.map((n) => [n.id, n])),\n  [allNodes],\n);\n\n\u002F\u002F Cache layout results\nconst layoutCacheRef = useRef\u003CMap\u003Cstring, Node[]>>(new Map());\n\n\u002F\u002F Proper cleanup in useEffect\nuseEffect(() => {\n  return () => {\n    \u002F\u002F Clean up any lingering references\n    nodesMapRef.current.clear();\n    edgesMapRef.current.clear();\n  };\n}, []);\n```\n\n### State Optimization\n\n```typescript\n\u002F\u002F Use useRef for objects that shouldn't trigger re-renders\nconst autoSaveDataRef = useRef({\n  nodes: [],\n  edges: [],\n  lastSaved: Date.now(),\n});\n\n\u002F\u002F Update properties without breaking reference\nconst updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {\n  autoSaveDataRef.current.nodes = newNodes;\n  autoSaveDataRef.current.edges = newEdges;\n  autoSaveDataRef.current.lastSaved = Date.now();\n};\n```\n\n## Common Problems & Solutions\n\n### Performance Issues\n\n- **Problem**: Lag during node expansion\n- **Solution**: Implement incremental rendering with change detection\n\n- **Problem**: Memory usage increases over time\n- **Solution**: Proper cleanup in useEffect hooks and use WeakMap for temporary data\n\n### Layout Conflicts\n\n- **Problem**: Manual positioning conflicts with auto-layout\n- **Solution**: Use controlled positioning state and separate layout modes\n\n### Rendering Issues\n\n- **Problem**: Excessive re-renders\n- **Solution**: Use memo, useMemo, and useCallback with stable dependencies\n\n- **Problem**: Slow layout calculations\n- **Solution**: Debounce layout calculations and cache results\n\n## Complete Example\n\n```typescript\nimport React, { useState, useCallback, useMemo, useRef } from 'react';\nimport ReactFlow, { Node, Edge, useReactFlow } from 'reactflow';\nimport dagre from 'dagre';\nimport { debounce } from 'lodash';\n\ninterface GraphState {\n  nodes: Node[];\n  edges: Edge[];\n  selectedNodeId: string | null;\n  expandedNodeIds: Set\u003Cstring>;\n  history: GraphState[];\n  historyIndex: number;\n}\n\nexport default function InteractiveGraph() {\n  const [state, setState] = useState\u003CGraphState>({\n    nodes: [],\n    edges: [],\n    selectedNodeId: null,\n    expandedNodeIds: new Set(),\n    history: [],\n    historyIndex: 0,\n  });\n\n  const { fitView } = useReactFlow();\n  const layoutCacheRef = useRef\u003CMap\u003Cstring, Node[]>>(new Map());\n\n  \u002F\u002F Memoized styled edges\n  const styledEdges = useMemo(() => {\n    return state.edges.map(edge => ({\n      ...edge,\n      style: {\n        ...edge.style,\n        strokeWidth: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? 3 : 2,\n        stroke: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target ? '#3b82f6' : '#94a3b8',\n      },\n      animated: state.selectedNodeId === edge.source || state.selectedNodeId === edge.target,\n    }));\n  }, [state.edges, state.selectedNodeId]);\n\n  \u002F\u002F Debounced layout calculation\n  const debouncedLayout = useMemo(\n    () => debounce((nodes: Node[], edges: Edge[]) => {\n      const cacheKey = generateLayoutCacheKey(nodes, edges);\n\n      if (layoutCacheRef.current.has(cacheKey)) {\n        return layoutCacheRef.current.get(cacheKey)!;\n      }\n\n      const layouted = applyDagreLayout(nodes, edges);\n      layoutCacheRef.current.set(cacheKey, layouted);\n\n      return layouted;\n    }, 150),\n    []\n  );\n\n  const handleNodeClick = useCallback((event: React.MouseEvent, node: Node) => {\n    setState(prev => ({\n      ...prev,\n      selectedNodeId: node.id,\n    }));\n  }, []);\n\n  const handleToggleExpand = useCallback((nodeId: string) => {\n    setState(prev => {\n      const newExpanded = new Set(prev.expandedNodeIds);\n      if (newExpanded.has(nodeId)) {\n        newExpanded.delete(nodeId);\n      } else {\n        newExpanded.add(nodeId);\n      }\n\n      return {\n        ...prev,\n        expandedNodeIds: newExpanded,\n      };\n    });\n  }, []);\n\n  return (\n    \u003CReactFlow\n      nodes={state.nodes}\n      edges={styledEdges}\n      onNodeClick={handleNodeClick}\n      fitView\n    \u002F>\n  );\n}\n```\n\nThis comprehensive skill provides everything needed to build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management patterns.\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n",{"name":5,"risk":31,"source":35,"date_added":49,"description":7},"2026-02-27","main","7a38d8e7073e61c16eda85681cb32fb68a8657a5","skills-store\u002Fcopilot\u002Freact-flow-architect","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router","https:\u002F\u002Fgithub.com\u002Fsapsapshen","2026-04-14 21:12:12",{"source":8,"skillName":5,"commands":57,"prompt":67,"zipAvailable":68},[58,61,64],{"tool":59,"command":60},"npx","npx skills add sapsapshen\u002Fai-memory-router --skill react-flow-architect",{"tool":62,"command":63},"bunx","bunx skills add sapsapshen\u002Fai-memory-router --skill react-flow-architect",{"tool":65,"command":66},"pnpm","pnpm dlx skills add sapsapshen\u002Fai-memory-router --skill react-flow-architect","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fcopilot\u002Freact-flow-architect\u002FSKILL.md",true,[],[71,94,120,130,143,160],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":82,"publishedAt":44},"01M71ATQRKG48SH06FQ697T5G9","wechat-search","Search WeChat Official Account articles using OpenClaw's web search and fetch capabilities with compliance-focused design.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fwechat-search-release\u002FSKILL.md",[77],{"categoryNo":78,"categoryName":79,"taxonomyType":80,"taxonomyTypeDesc":81},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[83,84,85,86,87,88,89,90,91,92,93,34],"wechat search","wechat","search","capabilities","openclaw","design","web search","compliance","web","official","fetch",{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":100,"publishedAt":44},"01M71ATNAVGZB35PFYM2GC5N00","trend-tap","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend tap', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-tap\u002FSKILL.md",[],[101,102,103,104,105,106,85,107,108,109,110,111,112,113,114,115,116,117,118,119],"trend","trend tap","tap","api keys","Bash","time","trending","hacker","aggregator","do","when","brew","live","data","google","install","read","across","api",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":126,"publishedAt":44},"01M71ATJ9AM5KM15AA8PS302EB","trend-radar","Real-time trending topics aggregator across 7 platforms (X\u002FTwitter, Reddit, Google Trends, Hacker News, Zhihu, Bilibili, Weibo). Trigger: when user says 'trend', 'trends', 'trend pulse', '热点', '热搜', or asks what is trending\u002Fhot. Do NOT use web_search — this skill already fetches live data from all platforms with zero API keys.","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftrend-radar\u002FSKILL.md",[],[127,128,101,112,113,114,115,105,85,117,129,118,119,111,109,108,107,116,106,104],"trend radar","radar","pulse",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":134,"categories":135,"keywords":136,"publishedAt":44},"01M71ATGF095N3EJM9G8SD0BB5","tech-news-daily","每日科技新闻汇——从全球多个 RSS 源抓取最新科技新闻，翻译（英→中）、去重、排版成每日简报。当用户询问科技新闻、科技新鲜事、每日科技、技术动态、Daily Tech News 或类似表达时触发此技能。","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Ftech-news-daily\u002FSKILL.md",[],[137,138,139,140,141,142],"news","tech","tech news daily","daily","rss","翻译",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":152,"publishedAt":44},"01M71ATFAEAMQ17M70NQNY7SR8","summarize","Summarize URLs or files with the summarize CLI (web, PDFs, images, audio, YouTube).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsummarize\u002FSKILL.md",[149],{"categoryNo":150,"categoryName":151,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000021","资料检索与研究",[145,91,153,154,155,34,112,156,157,158,103,159,116],"Audio","cli","youtube","images","https","files","pdfs",{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":166,"publishedAt":44},"01M71ATDNS3R0M4H936PACQAWZ","sonoscli","Control Sonos speakers (discover\u002Fstatus\u002Fplay\u002Fvolume\u002Fgroup).","https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fopenclaw\u002Fsonoscli\u002FSKILL.md",[],[162,167,168,169,116,170,157,171,172,173,174,175],"status","play","go","latest","github","discover","control","cmd","volume"]