| name | react-flow-architect |
| description | 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... |
| risk | unknown |
| source | community |
| date_added | "2026-02-27T00:00:00.000Z" |
ReactFlow Architect
Build production-ready ReactFlow applications with hierarchical navigation, performance optimization, and advanced state management.
Quick Start
Create basic interactive graph:
import 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}
/>
)
}
Core Patterns
Hierarchical Tree Navigation
Build expandable/collapsible tree structures with parent-child relationships.
Node Schema
interface TreeNode extends Node {
data: {
label: string
level: number
hasChildren: boolean
isExpanded: boolean
childCount: number
category: 'root' | 'category' | 'process' | 'detail'
}
}
Incremental Node Building
const buildVisibleNodes = useCallback(
(allNodes: TreeNode[], expandedIds: Set<string>, otherDeps: any[]) => {
const visibleNodes = new Map<string, TreeNode>()
const visibleEdges = new Map<string, TreeEdge>()
const rootNodes = allNodes.filter(n => n.data.level === 0)
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()),
}
},
[],
)
Performance Optimization
Handle large datasets with incremental rendering and memoization.
Incremental Rendering
const 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
const expandedChanged = !areSetsEqual(currentExpandedSet, prevExpandedSet)
const otherDepsChanged = !arraysEqual(otherDeps, prevOtherDepsRef.current)
if (expandedChanged && !otherDepsChanged) {
return buildIncrementalUpdate(
cachedVisibleNodesRef.current,
cachedVisibleEdgesRef.current,
allNodes,
allEdges,
currentExpandedSet,
prevExpandedSet,
)
} else {
return buildFullGraph(allNodes, allEdges, currentExpandedSet)
}
}, [allNodes, allEdges, expandedList, ...otherDeps])
return {visibleNodes, visibleEdges}
}
Memoization Patterns
const ProcessNode = memo(({ data, selected }: NodeProps) => {
return (
<div className={`process-node ${selected ? 'selected' : ''}`}>
{data.label}
</div>
);
}, (prevProps, nextProps) => {
return (
prevProps.data.label === nextProps.data.label &&
prevProps.selected === nextProps.selected &&
prevProps.data.isExpanded === nextProps.data.isExpanded
);
});
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]);
State Management
Complex node/edge state patterns with undo/redo and persistence.
Reducer Pattern
type 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
}
}
History Management
const 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}
}
Advanced Features
Auto-Layout Integration
Integrate Dagre for automatic graph layout:
import dagre from 'dagre'
const layoutOptions = {
rankdir: 'TB',
nodesep: 100,
ranksep: 150,
marginx: 50,
marginy: 50,
edgesep: 10,
}
const applyLayout = (nodes: Node[], edges: Edge[]) => {
const g = new dagre.graphlib.Graph()
g.setGraph(layoutOptions)
g.setDefaultEdgeLabel(() => ({}))
nodes.forEach(node => {
g.setNode(node.id, {width: 200, height: 100})
})
edges.forEach(edge => {
g.setEdge(edge.source, edge.target)
})
dagre.layout(g)
return nodes.map(node => ({
...node,
position: {
x: g.node(node.id).x - 100,
y: g.node(node.id).y - 50,
},
}))
}
const debouncedLayout = useMemo(() => debounce(applyLayout, 150), [])
Focus Mode
Isolate selected nodes and their direct connections:
const useFocusMode = (
selectedNodeId: string,
allNodes: Node[],
allEdges: Edge[],
) => {
return useMemo(() => {
if (!selectedNodeId) return {nodes: allNodes, edges: allEdges}
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)
}
})
const focusedNodes = allNodes.filter(n => connectedNodeIds.has(n.id))
return {nodes: focusedNodes, edges: focusedEdges}
}, [selectedNodeId, allNodes, allEdges])
}
const focusModeStyles = {
transition: 'all 0.3s ease-in-out',
opacity: isInFocus ? 1 : 0.3,
filter: isInFocus ? 'none' : 'blur(2px)',
}
Search Integration
Search and navigate to specific nodes:
const 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) => {
const nodePath = calculateBreadcrumbPath(nodeId, allNodes)
const parentIds = nodePath.slice(0, -1).map(n => n.id)
setExpandedIds(prev => new Set([...prev, ...parentIds]))
setSelectedNodeId(nodeId)
fitView({nodes: [{id: nodeId}], duration: 800})
}
Performance Tools
Graph Performance Analyzer
Create a performance analysis script:
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),
}
this.detectPerformanceIssues(analysis)
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)
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',
})
}
}
}
Best Practices
Performance Guidelines
- Use React.memo for node components to prevent unnecessary re-renders
- Implement virtualization for graphs with 1000+ nodes
- Debounce layout calculations during rapid interactions
- Use useCallback for edge creation and manipulation functions
- Implement proper TypeScript types for nodes and edges
Memory Management
const nodesById = useMemo(
() => new Map(allNodes.map(n => [n.id, n])),
[allNodes],
)
const layoutCacheRef = useRef<Map<string, Node[]>>(new Map())
useEffect(() => {
return () => {
nodesMapRef.current.clear()
edgesMapRef.current.clear()
}
}, [])
State Optimization
const autoSaveDataRef = useRef({
nodes: [],
edges: [],
lastSaved: Date.now(),
})
const updateAutoSaveData = (newNodes: Node[], newEdges: Edge[]) => {
autoSaveDataRef.current.nodes = newNodes
autoSaveDataRef.current.edges = newEdges
autoSaveDataRef.current.lastSaved = Date.now()
}
Common Problems & Solutions
Performance Issues
-
Problem: Lag during node expansion
-
Solution: Implement incremental rendering with change detection
-
Problem: Memory usage increases over time
-
Solution: Proper cleanup in useEffect hooks and use WeakMap for temporary data
Layout Conflicts
- Problem: Manual positioning conflicts with auto-layout
- Solution: Use controlled positioning state and separate layout modes
Rendering Issues
-
Problem: Excessive re-renders
-
Solution: Use memo, useMemo, and useCallback with stable dependencies
-
Problem: Slow layout calculations
-
Solution: Debounce layout calculations and cache results
Complete Example
import 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());
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]);
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.
When to Use
This skill is applicable to execute the workflow or actions described in the overview.