Help us improve
Share bugs, ideas, or general feedback.
From beagle-react
Reviews React Flow (@xyflow/react) code for anti-patterns, performance issues, and best practices. Checks provider boundaries, stable nodeTypes/edgeTypes, memoization, and inline callbacks.
npx claudepluginhub existential-birds/beagle --plugin beagle-reactHow this skill is triggered — by the user, by Claude, or both
Slash command
/beagle-react:react-flow-code-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
When reviewing React Flow code, complete the gates below in order. Each step has an objective pass condition before moving on.
Builds ReactFlow applications with hierarchical tree navigation, incremental rendering, and memoized state management for large graphs.
Provides architectural guidance for building node-based UIs with React Flow, covering use-case evaluation, state management strategies, and package structure.
Audits React and Next.js code for six critical pitfalls: nested components, index keys, derived state in effects, unsafe fetching, unmemoized contexts, and more.
Share bugs, ideas, or general feedback.
When reviewing React Flow code, complete the gates below in order. Each step has an objective pass condition before moving on.
Locate flow code — Search the review scope for ReactFlow, ReactFlowProvider, useReactFlow, @xyflow/react, nodeTypes, and edgeTypes. Pass: a short list of file paths (or explicit “none in scope” after searching).
Provider boundary — For each useReactFlow() (and other hooks that require the provider), trace the component tree to an enclosing ReactFlowProvider, or record a concrete mismatch with file:line.
Stable types and memo surfaces — For each custom node or edge component, note whether it uses memo and typed props (NodeProps<...>, etc.). For each nodeTypes / edgeTypes value passed into <ReactFlow>, confirm a stable reference (module scope, or useMemo with deps you can point to) or flag unstable recreation with file:line.
Report with evidence — For each finding you will deliver, record file path and line number(s) (or a minimal quoted snippet). Pass: no critical or high-severity issue is stated without that citation.
Close the checklists — Use Performance Checklist and Common Mistakes; each item is satisfied, not applicable (with reason), or open with evidence. Pass: no item left silently ambiguous.
Problem: Causes all nodes to re-mount on every render.
// BAD - recreates object every render
function Flow() {
const nodeTypes = { custom: CustomNode }; // WRONG
return <ReactFlow nodeTypes={nodeTypes} />;
}
// GOOD - defined outside component
const nodeTypes = { custom: CustomNode };
function Flow() {
return <ReactFlow nodeTypes={nodeTypes} />;
}
// GOOD - useMemo if dynamic
function Flow() {
const nodeTypes = useMemo(() => ({ custom: CustomNode }), []);
return <ReactFlow nodeTypes={nodeTypes} />;
}
Problem: Custom components re-render on every parent update.
// BAD - no memoization
function CustomNode({ data }: NodeProps) {
return <div>{data.label}</div>;
}
// GOOD - wrapped in memo
import { memo } from 'react';
const CustomNode = memo(function CustomNode({ data }: NodeProps) {
return <div>{data.label}</div>;
});
Problem: Creates new function references, breaking memoization.
// BAD - inline callback
<ReactFlow
onNodesChange={(changes) => setNodes(applyNodeChanges(changes, nodes))}
/>
// GOOD - memoized callback
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[]
);
<ReactFlow onNodesChange={onNodesChange} />
// BAD - will throw error
function App() {
const { getNodes } = useReactFlow(); // ERROR: No provider
return <ReactFlow ... />;
}
// GOOD - wrap in provider
function FlowContent() {
const { getNodes } = useReactFlow(); // Works
return <ReactFlow ... />;
}
function App() {
return (
<ReactFlowProvider>
<FlowContent />
</ReactFlowProvider>
);
}
Problem: Reference equality checks fail, causing unnecessary updates.
// BAD - new object reference every time
setNodes(nodes.map(n => ({
...n,
data: { ...n.data, config: { nested: 'value' } } // New object each time
})));
// GOOD - use updateNodeData for targeted updates
const { updateNodeData } = useReactFlow();
updateNodeData(nodeId, { config: { nested: 'value' } });
memo()useMemouseCallbackmemo()setNodes((nds) => ...)updateNodeData for data-only changesfitView() on every renderfitViewOptions for initial fit only// BAD - no height, flow won't render
<ReactFlow nodes={nodes} edges={edges} />
// GOOD - explicit dimensions
<div style={{ width: '100%', height: '100vh' }}>
<ReactFlow nodes={nodes} edges={edges} />
</div>
// Required for default styles
import '@xyflow/react/dist/style.css';
// BAD - clicking button drags node
<button onClick={handleClick}>Click</button>
// GOOD - prevents drag
<button className="nodrag" onClick={handleClick}>Click</button>
// BAD - string literals
<Handle type="source" position="right" />
// GOOD - type-safe constants
import { Position } from '@xyflow/react';
<Handle type="source" position={Position.Right} />
// BAD - direct mutation
nodes[0].position = { x: 100, y: 100 };
setNodes(nodes);
// GOOD - immutable update
setNodes(nodes.map(n =>
n.id === '1' ? { ...n, position: { x: 100, y: 100 } } : n
));
// BAD - loses type safety
const [nodes, setNodes] = useNodesState(initialNodes);
// GOOD - explicit types
type MyNode = Node<{ value: number }, 'custom'>;
const [nodes, setNodes] = useNodesState<MyNode>(initialNodes);
// BAD - using wrong type
function CustomNode(props: any) { ... }
// GOOD - correct props type
function CustomNode(props: NodeProps<MyNode>) { ... }