
React Flow
- 1.1k installs
- 74 repo stars
- Updated July 21, 2026
- existential-birds/beagle
react-flow is an agent skill for React Flow graph UIs in the Beagle project.
About
The react-flow skill guides React Flow integration within the Beagle project for node-based graph editors, diagrams, and interactive canvases. It covers installing and configuring React Flow components, custom node and edge types, layout helpers, and state synchronization patterns suited to Beagle's architecture. Agents learn viewport controls, drag-and-drop node placement, connection validation, and performance considerations for medium-sized graphs. The skill aligns UI patterns with Beagle conventions for styling, data fetching into graph nodes, and persisting graph state back to application stores or APIs.
- React Flow node-based graph UI patterns for Beagle.
- Custom nodes, edges, and connection validation guidance.
- Viewport controls and drag-and-drop placement patterns.
- Performance notes for medium-sized interactive graphs.
- Aligns graph state with Beagle data stores and APIs.
React Flow by the numbers
- 1,084 all-time installs (skills.sh)
- +14 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #351 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
What react-flow says it does
React Flow
npx skills add https://github.com/existential-birds/beagle --skill react-flowAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 74 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 21, 2026 |
| Repository | existential-birds/beagle ↗ |
How should I build a node-based diagram UI in Beagle with React Flow?
Implement node-based graph UIs with React Flow in the Beagle project.
Who is it for?
Beagle frontend developers adding graph or flowchart interfaces.
Skip if: Skip for non-Beagle projects or static diagrams without React Flow.
When should I use this skill?
User builds node editors, flow diagrams, or interactive canvases in Beagle.
What you get
Configured React Flow canvas with custom nodes, edges, and persisted graph state.
- custom edge components
- edge type registration
Files
React Flow
React Flow (@xyflow/react) is a library for building node-based graphs, workflow editors, and interactive diagrams. It provides a highly customizable framework for creating visual programming interfaces, process flows, and network visualizations.
Quick Start
Installation
pnpm add @xyflow/reactBasic Setup
import { ReactFlow, Node, Edge, Background, Controls, MiniMap } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes: Node[] = [
{
id: '1',
type: 'input',
data: { label: 'Input Node' },
position: { x: 250, y: 5 },
},
{
id: '2',
data: { label: 'Default Node' },
position: { x: 100, y: 100 },
},
{
id: '3',
type: 'output',
data: { label: 'Output Node' },
position: { x: 400, y: 100 },
},
];
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e2-3', source: '2', target: '3' },
];
function Flow() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow nodes={initialNodes} edges={initialEdges}>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
</div>
);
}
export default Flow;Core Concepts
Nodes
Nodes are the building blocks of the graph. Each node has:
id: Unique identifiertype: Node type (built-in or custom)position: { x, y } coordinatesdata: Custom data object
import { Node } from '@xyflow/react';
const node: Node = {
id: 'node-1',
type: 'default',
position: { x: 100, y: 100 },
data: { label: 'Node Label' },
style: { background: '#D6D5E6' },
className: 'custom-node',
};Built-in node types:
default: Standard nodeinput: No target handlesoutput: No source handlesgroup: Container for other nodes
Edges
Edges connect nodes. Each edge requires:
id: Unique identifiersource: Source node IDtarget: Target node ID
import { Edge } from '@xyflow/react';
const edge: Edge = {
id: 'e1-2',
source: '1',
target: '2',
type: 'smoothstep',
animated: true,
label: 'Edge Label',
style: { stroke: '#fff', strokeWidth: 2 },
};Built-in edge types:
default: Bezier curvestraight: Straight linestep: Orthogonal with sharp cornerssmoothstep: Orthogonal with rounded corners
Handles
Handles are connection points on nodes. Use Position enum for placement:
import { Handle, Position } from '@xyflow/react';
<Handle type="target" position={Position.Top} />
<Handle type="source" position={Position.Bottom} />Available positions: Position.Top, Position.Right, Position.Bottom, Position.Left
State Management
Controlled Flow
Use state hooks for full control:
import { useNodesState, useEdgesState, addEdge, OnConnect } from '@xyflow/react';
import { useCallback } from 'react';
function ControlledFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect: OnConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
);
}useReactFlow Hook
Access the React Flow instance for programmatic control:
import { useReactFlow } from '@xyflow/react';
function FlowControls() {
const {
getNodes,
getEdges,
setNodes,
setEdges,
addNodes,
addEdges,
deleteElements,
fitView,
zoomIn,
zoomOut,
getNode,
getEdge,
updateNode,
updateEdge,
} = useReactFlow();
return (
<button onClick={() => fitView()}>Fit View</button>
);
}Custom Nodes
Define custom nodes using NodeProps<T> with typed data:
import { NodeProps, Node, Handle, Position } from '@xyflow/react';
export type CustomNode = Node<{ label: string; status: 'active' | 'inactive' }, 'custom'>;
function CustomNodeComponent({ data, selected }: NodeProps<CustomNode>) {
return (
<div className={`px-4 py-2 ${selected ? 'ring-2' : ''}`}>
<Handle type="target" position={Position.Top} />
<div className="font-bold">{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</div>
);
}Register with nodeTypes:
const nodeTypes: NodeTypes = { custom: CustomNodeComponent };
<ReactFlow nodeTypes={nodeTypes} />Key Patterns
- Multiple Handles: Use
idprop andstylefor positioning - Dynamic Handles: Call
useUpdateNodeInternals([nodeId])after adding/removing handles - Interactive Elements: Add
className="nodrag"to prevent dragging on inputs/buttons
See Custom Nodes Reference for detailed patterns including styling, aviation map pins, and dynamic handles.
Custom Edges
Define custom edges using EdgeProps<T> and path utilities:
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
export type CustomEdge = Edge<{ status: 'normal' | 'error' }, 'custom'>;
function CustomEdgeComponent(props: EdgeProps<CustomEdge>) {
const [edgePath] = getBezierPath(props);
return (
<BaseEdge
id={props.id}
path={edgePath}
style={{ stroke: props.data?.status === 'error' ? '#ef4444' : '#64748b' }}
/>
);
}Path Utilities
getBezierPath()- Smooth curvesgetStraightPath()- Straight linesgetSmoothStepPath()- Orthogonal with rounded cornersgetSmoothStepPath({ borderRadius: 0 })- Orthogonal with sharp corners (step edge)
All return [path, labelX, labelY, offsetX, offsetY].
Interactive Labels
Use EdgeLabelRenderer for HTML-based labels with pointer events:
import { EdgeLabelRenderer, BaseEdge, getBezierPath } from '@xyflow/react';
function ButtonEdge(props: EdgeProps) {
const [edgePath, labelX, labelY] = getBezierPath(props);
return (
<>
<BaseEdge id={props.id} path={edgePath} />
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
pointerEvents: 'all',
}}
className="nodrag nopan"
>
<button onClick={() => console.log('Delete')}>×</button>
</div>
</EdgeLabelRenderer>
</>
);
}See Custom Edges Reference for animated edges, time labels, and SVG text patterns.
Viewport Control
Use useReactFlow() hook for programmatic viewport control:
import { useReactFlow } from '@xyflow/react';
function ViewportControls() {
const { fitView, zoomIn, zoomOut, setCenter, screenToFlowPosition } = useReactFlow();
// Fit all nodes in view
const handleFitView = () => fitView({ padding: 0.2, duration: 400 });
// Zoom controls
const handleZoomIn = () => zoomIn({ duration: 300 });
const handleZoomOut = () => zoomOut({ duration: 300 });
// Center on specific coordinates
const handleCenter = () => setCenter(250, 250, { zoom: 1.5, duration: 500 });
// Convert screen coordinates to flow coordinates
const addNodeAtClick = (event: React.MouseEvent) => {
const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
// Use position to add node
};
return null;
}See Viewport Reference for save/restore state, controlled viewport, and coordinate transformations.
Events
React Flow provides comprehensive event handling:
Node Events
import { NodeMouseHandler, OnNodeDrag } from '@xyflow/react';
const onNodeClick: NodeMouseHandler = (event, node) => {
console.log('Node clicked:', node.id);
};
const onNodeDrag: OnNodeDrag = (event, node, nodes) => {
console.log('Dragging:', node.id);
};
<ReactFlow
onNodeClick={onNodeClick}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeClick}
/>Edge and Connection Events
import { EdgeMouseHandler, OnConnect } from '@xyflow/react';
const onEdgeClick: EdgeMouseHandler = (event, edge) => console.log('Edge:', edge.id);
const onConnect: OnConnect = (connection) => console.log('Connected:', connection);
<ReactFlow onEdgeClick={onEdgeClick} onConnect={onConnect} />Selection and Viewport Events
import { useOnSelectionChange, useOnViewportChange } from '@xyflow/react';
useOnSelectionChange({
onChange: ({ nodes, edges }) => console.log('Selected:', nodes.length, edges.length),
});
useOnViewportChange({
onChange: (viewport) => console.log('Viewport:', viewport.zoom),
});See Events Reference for complete event catalog including validation, deletion, and error handling.
Common Patterns
Preventing Drag/Pan
<input className="nodrag" />
<button className="nodrag nopan">Click me</button>Connection Validation
const isValidConnection = (connection: Connection) => {
return connection.source !== connection.target; // Prevent self-connections
};
<ReactFlow isValidConnection={isValidConnection} />Adding Nodes on Click
const { screenToFlowPosition, setNodes } = useReactFlow();
const onPaneClick = (event: React.MouseEvent) => {
const position = screenToFlowPosition({ x: event.clientX, y: event.clientY });
setNodes(nodes => [...nodes, { id: `node-${Date.now()}`, position, data: { label: 'New' } }]);
};Updating Node Data
const { updateNodeData } = useReactFlow();
updateNodeData('node-1', { label: 'Updated' });
updateNodeData('node-1', (node) => ({ ...node.data, count: node.data.count + 1 }));Provider Pattern
Wrap the app with ReactFlowProvider when using useReactFlow() outside the flow:
import { ReactFlow, ReactFlowProvider, useReactFlow } from '@xyflow/react';
function Controls() {
const { fitView } = useReactFlow(); // Must be inside provider
return <button onClick={() => fitView()}>Fit View</button>;
}
function App() {
return (
<ReactFlowProvider>
<Controls />
<ReactFlow nodes={nodes} edges={edges} />
</ReactFlowProvider>
);
}Implementation gates
Use these sequenced checks before treating an integration as done (they target common footguns, not style preferences).
1. CSS in the bundle — Ensure import '@xyflow/react/dist/style.css' runs in the app (entry or layout). Pass: nodes and edges have expected default styling; handles are visible and interactable. 2. Stable `nodeTypes` / `edgeTypes` — Do not pass a fresh object literal every render; define maps outside the component or memoize with useMemo and correct deps. Pass: no remount flicker or “maximum update depth” / runaway updates when only selection or viewport changes. 3. Provider boundary — Components that call useReactFlow() must be descendants of ReactFlowProvider, and the flow must actually mount. Pass: no missing-context error at runtime; programmatic APIs (fitView, etc.) work where expected.
Reference Files
For detailed implementation patterns, see:
- Custom Nodes - NodeProps typing, Handle component, dynamic handles, styling patterns
- Custom Edges - EdgeProps typing, path utilities, EdgeLabelRenderer, animated edges
- Viewport - useReactFlow methods, fitView options, coordinate conversion
- Events - Node/edge/connection events, selection handling, viewport changes
Custom Edges
Custom edges in React Flow use the EdgeProps<T> typing pattern and path utility functions to render connections between nodes.
Table of Contents
- Edge Type Definition
- EdgeProps Structure
- Path Utility Functions
- BaseEdge Component
- EdgeLabelRenderer for Interactive Labels
- Animated Edges
- SVG Text Labels
- EdgeText Component
- Time Label Edge Example
- Edge Registration
- Default Edge Options
Edge Type Definition
Define custom edge types with typed data:
import { Edge, EdgeProps } from '@xyflow/react';
// Define the custom edge type
export type TimeLabelEdge = Edge<{ time: string; label: string }, 'timeLabel'>;
// Component receives EdgeProps
export default function TimeLabelEdge(props: EdgeProps<TimeLabelEdge>) {
// Edge implementation
}EdgeProps Structure
The EdgeProps type includes these key properties:
type EdgeProps<T extends Edge = Edge> = {
id: string;
type?: string;
source: string;
target: string;
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
sourcePosition: Position;
targetPosition: Position;
data?: T['data'];
selected?: boolean;
animated?: boolean;
style?: CSSProperties;
markerStart?: string;
markerEnd?: string;
sourceHandleId?: string | null;
targetHandleId?: string | null;
label?: ReactNode;
labelStyle?: CSSProperties;
labelShowBg?: boolean;
labelBgStyle?: CSSProperties;
labelBgPadding?: [number, number];
labelBgBorderRadius?: number;
interactionWidth?: number;
pathOptions?: any;
};Path Utility Functions
React Flow provides several path generators:
getBezierPath
Creates smooth curved paths:
import { FC } from 'react';
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
const CustomEdge: FC<EdgeProps> = ({
id,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
data,
}) => {
const [edgePath, labelX, labelY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
curvature: 0.25, // Optional: control curve amount (default 0.25)
});
return <BaseEdge path={edgePath} id={id} />;
};getStraightPath
Creates direct straight lines:
import { getStraightPath } from '@xyflow/react';
const [edgePath, labelX, labelY] = getStraightPath({
sourceX,
sourceY,
targetX,
targetY,
});getSmoothStepPath
Creates orthogonal paths with smooth corners:
import { getSmoothStepPath } from '@xyflow/react';
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
borderRadius: 8, // Optional: corner radius
offset: 20, // Optional: offset from node
});getSmoothStepPath with borderRadius: 0 (Step Edge)
For orthogonal paths with sharp corners, use getSmoothStepPath with borderRadius: 0:
import { getSmoothStepPath } from '@xyflow/react';
const [edgePath, labelX, labelY] = getSmoothStepPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
borderRadius: 0, // Sharp corners (step edge)
offset: 20, // Optional: offset from node
});BaseEdge Component
The BaseEdge component renders the path with proper styling:
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
function CustomEdge(props: EdgeProps) {
const [edgePath] = getBezierPath(props);
return (
<BaseEdge
id={props.id}
path={edgePath}
style={props.style}
markerEnd={props.markerEnd}
markerStart={props.markerStart}
interactionWidth={20} // Wider click target
/>
);
}EdgeLabelRenderer for Interactive Labels
Use EdgeLabelRenderer to render interactive HTML labels instead of SVG text:
import { getBezierPath, EdgeLabelRenderer, BaseEdge, EdgeProps } from '@xyflow/react';
function CustomEdge({ id, data, ...props }: EdgeProps) {
const [edgePath, labelX, labelY] = getBezierPath(props);
return (
<>
<BaseEdge id={id} path={edgePath} />
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
background: '#ffcc00',
padding: 10,
borderRadius: 5,
fontSize: 12,
fontWeight: 700,
pointerEvents: 'all', // Enable interactions
}}
className="nodrag nopan"
>
<button onClick={() => console.log('clicked edge', id)}>
{data?.label || 'Delete'}
</button>
</div>
</EdgeLabelRenderer>
</>
);
}Animated Edges
Dash Animation
Animate the stroke dash pattern:
const animatedEdgeStyle = {
strokeDasharray: '5 5',
animation: 'dashdraw 0.5s linear infinite',
};
// CSS
// @keyframes dashdraw {
// to {
// stroke-dashoffset: -10;
// }
// }
function AnimatedEdge(props: EdgeProps) {
const [edgePath] = getBezierPath(props);
return <BaseEdge path={edgePath} style={animatedEdgeStyle} />;
}Moving Circle Along Path
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
function MovingCircleEdge(props: EdgeProps) {
const [edgePath] = getBezierPath(props);
return (
<>
<BaseEdge id={props.id} path={edgePath} />
<circle r="4" fill="#ff0072">
<animateMotion dur="2s" repeatCount="indefinite" path={edgePath} />
</circle>
</>
);
}SVG Text Labels
For simple text labels along the path:
import { BaseEdge, EdgeProps, getBezierPath } from '@xyflow/react';
function TextLabelEdge({ id, data, ...props }: EdgeProps) {
const [edgePath] = getBezierPath(props);
return (
<>
<BaseEdge path={edgePath} id={id} />
<text>
<textPath
href={`#${id}`}
style={{ fontSize: '12px' }}
startOffset="50%"
textAnchor="middle"
>
{data?.text || ''}
</textPath>
</text>
</>
);
}EdgeText Component
For positioned text with background:
import { BaseEdge, EdgeText, EdgeProps, getSmoothStepPath } from '@xyflow/react';
function LabeledEdge({ id, data, ...props }: EdgeProps) {
const [edgePath, labelX, labelY] = getSmoothStepPath(props);
return (
<>
<BaseEdge id={id} path={edgePath} />
<EdgeText
x={labelX}
y={labelY - 5}
label={data?.text || ''}
labelBgStyle={{ fill: 'white' }}
labelStyle={{ fill: 'black' }}
onClick={() => console.log(data)}
/>
</>
);
}Time Label Edge Example
Custom edge displaying time/duration labels:
import { EdgeProps, getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react';
type TimeLabelData = {
duration: string;
status: 'normal' | 'delayed' | 'critical';
};
export type TimeLabelEdge = Edge<TimeLabelData, 'timeLabel'>;
function TimeLabelEdge({ id, data, selected, ...props }: EdgeProps<TimeLabelEdge>) {
const [edgePath, labelX, labelY] = getBezierPath(props);
const statusColors = {
normal: 'bg-green-100 text-green-800',
delayed: 'bg-yellow-100 text-yellow-800',
critical: 'bg-red-100 text-red-800',
};
return (
<>
<BaseEdge
id={id}
path={edgePath}
style={{
strokeWidth: selected ? 2 : 1,
stroke: data?.status === 'critical' ? '#ef4444' : undefined,
}}
/>
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
pointerEvents: 'all',
}}
className="nodrag nopan"
>
<div className={`px-2 py-1 rounded text-xs font-medium ${statusColors[data?.status || 'normal']}`}>
{data?.duration || '0m'}
</div>
</div>
</EdgeLabelRenderer>
</>
);
}Edge Registration
Register custom edges in the edgeTypes prop:
import { ReactFlow, EdgeTypes } from '@xyflow/react';
import TimeLabelEdge from './TimeLabelEdge';
import AnimatedEdge from './AnimatedEdge';
const edgeTypes: EdgeTypes = {
timeLabel: TimeLabelEdge,
animated: AnimatedEdge,
};
function Flow() {
return (
<ReactFlow
nodes={nodes}
edges={edges}
edgeTypes={edgeTypes}
/>
);
}Default Edge Options
Set default properties for all edges:
import { DefaultEdgeOptions } from '@xyflow/react';
const defaultEdgeOptions: DefaultEdgeOptions = {
animated: true,
type: 'smoothstep',
style: { stroke: '#fff', strokeWidth: 2 },
};
<ReactFlow defaultEdgeOptions={defaultEdgeOptions} />Custom Nodes
React Flow custom nodes use the NodeProps<T> typing pattern where T is the specific node type with custom data.
Table of Contents
- Node Type Definition
- Handle Component
- Multiple Handles
- Dynamic Handles with useUpdateNodeInternals
- Styling Nodes
- Aviation Map Pin Node Example
- Preventing Drag and Pan
- Node Registration
Node Type Definition
Define custom nodes with typed data and specify the node type string:
import { Node, NodeProps } from '@xyflow/react';
// Define the custom node type
export type CounterNode = Node<{ initialCount?: number }, 'counter'>;
// Component receives NodeProps<CounterNode>
export default function CounterNode(props: NodeProps<CounterNode>) {
const [count, setCount] = useState(props.data?.initialCount ?? 0);
return (
<div>
<p>Count: {count}</p>
<button className="nodrag" onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}Handle Component
The Handle component defines connection points on nodes. Use type="target" for incoming connections and type="source" for outgoing connections.
import { Handle, Position } from '@xyflow/react';
function CustomNode({ data }) {
return (
<>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
}Multiple Handles
Use the id prop to create multiple handles on a single node:
import { Handle, Position, CSSProperties } from '@xyflow/react';
const sourceHandleStyleA: CSSProperties = { top: 10 };
const sourceHandleStyleB: CSSProperties = { bottom: 10, top: 'auto' };
function MultiHandleNode({ data, isConnectable }: NodeProps<ColorSelectorNode>) {
return (
<>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
{/* Multiple source handles with IDs */}
<Handle
type="source"
position={Position.Right}
id="a"
style={sourceHandleStyleA}
isConnectable={isConnectable}
/>
<Handle
type="source"
position={Position.Right}
id="b"
style={sourceHandleStyleB}
isConnectable={isConnectable}
/>
</>
);
}Dynamic Handles with useUpdateNodeInternals
When adding or removing handles dynamically, use useUpdateNodeInternals() to notify React Flow:
import { useState, useMemo } from 'react';
import { Handle, Position, useUpdateNodeInternals, NodeProps } from '@xyflow/react';
function DynamicHandleNode({ id }: NodeProps) {
const [handleCount, setHandleCount] = useState(1);
const updateNodeInternals = useUpdateNodeInternals();
const handles = useMemo(
() =>
Array.from({ length: handleCount }, (x, i) => {
const handleId = `handle-${i}`;
return (
<Handle
key={handleId}
type="source"
position={Position.Right}
id={handleId}
style={{ top: 10 * i }}
/>
);
}),
[handleCount]
);
return (
<div>
<Handle type="target" position={Position.Left} />
<div>output handle count: {handleCount}</div>
<button
onClick={() => {
setHandleCount((c) => c + 1);
updateNodeInternals(id); // Critical: notify React Flow
}}
>
add handle
</button>
{handles}
</div>
);
}Styling Nodes
CSS Classes
Apply styles with className and style props on the node definition:
const nodes: Node[] = [
{
id: '1',
type: 'custom',
data: { label: 'Styled Node' },
position: { x: 250, y: 5 },
style: { border: '1px solid #777', padding: 10 },
className: 'custom-node',
},
];Inline Styles in Component
import { CSSProperties } from 'react';
const nodeStyles: CSSProperties = { padding: 10, border: '1px solid #ddd' };
function StyledNode({ data }: NodeProps) {
return (
<div style={nodeStyles}>
{data.label}
</div>
);
}Tailwind CSS
React Flow works seamlessly with Tailwind:
function TailwindNode({ data }: NodeProps) {
return (
<div className="px-4 py-2 shadow-md rounded-md bg-white border-2 border-stone-400">
<div className="flex">
<div className="ml-2">
<div className="text-lg font-bold">{data.name}</div>
<div className="text-gray-500">{data.job}</div>
</div>
</div>
<Handle type="target" position={Position.Top} className="w-16 !bg-teal-500" />
<Handle type="source" position={Position.Bottom} className="w-16 !bg-teal-500" />
</div>
);
}Aviation Map Pin Node Example
Custom node with status-based styling using data-driven approach:
import { NodeProps, Handle, Position } from '@xyflow/react';
type MapPinData = {
label: string;
status: 'active' | 'warning' | 'inactive';
coordinate: { lat: number; lon: number };
};
export type MapPinNode = Node<MapPinData, 'mapPin'>;
function MapPinNode({ data, selected }: NodeProps<MapPinNode>) {
const statusColors = {
active: 'bg-green-500',
warning: 'bg-yellow-500',
inactive: 'bg-gray-400',
};
return (
<div className={`relative ${selected ? 'ring-2 ring-blue-500' : ''}`}>
{/* Beacon glow for active status */}
{data.status === 'active' && (
<div className="absolute inset-0 animate-ping bg-green-500 rounded-full opacity-75" />
)}
{/* Pin icon */}
<div className={`relative w-8 h-8 rounded-full ${statusColors[data.status]}`}>
<div className="absolute inset-0 flex items-center justify-center text-white font-bold">
{data.label}
</div>
</div>
{/* Connection handle at bottom */}
<Handle type="source" position={Position.Bottom} className="opacity-0" />
</div>
);
}Preventing Drag and Pan
Use nodrag and nopan classes to prevent interactions on specific elements:
function InteractiveNode({ data }: NodeProps) {
return (
<div>
<input
className="nodrag"
type="text"
defaultValue={data.label}
/>
<button className="nodrag nopan" onClick={() => console.log('clicked')}>
Click me
</button>
</div>
);
}Node Registration
Register custom nodes in the nodeTypes prop:
import { ReactFlow, NodeTypes } from '@xyflow/react';
import CustomNode from './CustomNode';
import MapPinNode from './MapPinNode';
const nodeTypes: NodeTypes = {
custom: CustomNode,
mapPin: MapPinNode,
};
function Flow() {
return (
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
/>
);
}Events
React Flow provides comprehensive event handling for nodes, edges, connections, selections, and viewport changes.
Table of Contents
- Node Events
- Click Events
- Drag Events
- Hover Events
- Edge Events
- Click Events
- Hover Events
- Edge Update and Reconnect
- Connection Events
- Basic Connection
- Connection Start and End
- Validate Connections
- Selection Events
- useOnSelectionChange Hook
- Selection Drag
- Selection Context Menu
- Viewport Events
- useOnViewportChange Hook
- Move Events
- Pane Events
- Click Events
- Mouse Events
- Init and Delete Events
- Initialization
- Delete Events
- Error Handling
Node Events
Click Events
import { ReactFlow, NodeMouseHandler, Node } from '@xyflow/react';
function NodeClickExample() {
const onNodeClick: NodeMouseHandler = (event, node) => {
console.log('Node clicked:', node.id, node.data);
};
const onNodeDoubleClick: NodeMouseHandler = (event, node) => {
console.log('Node double-clicked:', node.id);
};
const onNodeContextMenu: NodeMouseHandler = (event, node) => {
event.preventDefault();
console.log('Node right-clicked:', node.id);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodeClick={onNodeClick}
onNodeDoubleClick={onNodeDoubleClick}
onNodeContextMenu={onNodeContextMenu}
/>
);
}Drag Events
import { ReactFlow, OnNodeDrag, NodeMouseHandler } from '@xyflow/react';
function NodeDragExample() {
const onNodeDragStart: NodeMouseHandler = (event, node) => {
console.log('Drag started:', node.id);
};
const onNodeDrag: OnNodeDrag = (event, node, nodes) => {
console.log('Dragging:', node.id, 'at', node.position);
console.log('All dragged nodes:', nodes.map(n => n.id));
};
const onNodeDragStop: NodeMouseHandler = (event, node) => {
console.log('Drag stopped:', node.id, 'at', node.position);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodeDragStart={onNodeDragStart}
onNodeDrag={onNodeDrag}
onNodeDragStop={onNodeDragStop}
/>
);
}Hover Events
import { ReactFlow, NodeMouseHandler } from '@xyflow/react';
function NodeHoverExample() {
const onNodeMouseEnter: NodeMouseHandler = (event, node) => {
console.log('Mouse entered:', node.id);
};
const onNodeMouseMove: NodeMouseHandler = (event, node) => {
console.log('Mouse moving over:', node.id);
};
const onNodeMouseLeave: NodeMouseHandler = (event, node) => {
console.log('Mouse left:', node.id);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
/>
);
}Edge Events
Click Events
import { ReactFlow, EdgeMouseHandler } from '@xyflow/react';
function EdgeClickExample() {
const onEdgeClick: EdgeMouseHandler = (event, edge) => {
console.log('Edge clicked:', edge.id);
console.log('From:', edge.source, 'To:', edge.target);
};
const onEdgeDoubleClick: EdgeMouseHandler = (event, edge) => {
console.log('Edge double-clicked:', edge.id);
};
const onEdgeContextMenu: EdgeMouseHandler = (event, edge) => {
event.preventDefault();
console.log('Edge right-clicked:', edge.id);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onEdgeClick={onEdgeClick}
onEdgeDoubleClick={onEdgeDoubleClick}
onEdgeContextMenu={onEdgeContextMenu}
/>
);
}Hover Events
import { ReactFlow, EdgeMouseHandler } from '@xyflow/react';
function EdgeHoverExample() {
const onEdgeMouseEnter: EdgeMouseHandler = (event, edge) => {
console.log('Mouse entered edge:', edge.id);
};
const onEdgeMouseMove: EdgeMouseHandler = (event, edge) => {
console.log('Mouse moving over edge:', edge.id);
};
const onEdgeMouseLeave: EdgeMouseHandler = (event, edge) => {
console.log('Mouse left edge:', edge.id);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onEdgeMouseEnter={onEdgeMouseEnter}
onEdgeMouseMove={onEdgeMouseMove}
onEdgeMouseLeave={onEdgeMouseLeave}
/>
);
}Edge Update and Reconnect
import { ReactFlow, OnReconnect, OnReconnectStart, OnReconnectEnd } from '@xyflow/react';
function EdgeReconnectExample() {
const onReconnect: OnReconnect = (oldEdge, newConnection) => {
console.log('Edge reconnected:', oldEdge.id);
console.log('New connection:', newConnection);
};
const onReconnectStart: OnReconnectStart = (event, edge, handleType) => {
console.log('Reconnect started:', edge.id, 'handle:', handleType);
};
const onReconnectEnd: OnReconnectEnd = (event, edge, handleType, connectionState) => {
console.log('Reconnect ended:', edge.id);
console.log('Connection state:', connectionState);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}
onReconnectEnd={onReconnectEnd}
edgesReconnectable={true}
/>
);
}Connection Events
Basic Connection
import { ReactFlow, OnConnect, addEdge } from '@xyflow/react';
import { useCallback } from 'react';
function ConnectionExample() {
const [edges, setEdges] = useState<Edge[]>([]);
const onConnect: OnConnect = useCallback(
(connection) => {
console.log('Connection made:', connection);
console.log('Source:', connection.source);
console.log('Target:', connection.target);
console.log('Source Handle:', connection.sourceHandle);
console.log('Target Handle:', connection.targetHandle);
setEdges((eds) => addEdge(connection, eds));
},
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onConnect={onConnect}
/>
);
}Connection Start and End
import { ReactFlow, OnConnectStart, OnConnectEnd } from '@xyflow/react';
function ConnectionLifecycleExample() {
const onConnectStart: OnConnectStart = (event, { nodeId, handleId, handleType }) => {
console.log('Connection started from:', nodeId);
console.log('Handle:', handleId, 'Type:', handleType);
};
const onConnectEnd: OnConnectEnd = (event, connectionState) => {
console.log('Connection ended');
console.log('Was valid:', connectionState.isValid);
console.log('From node:', connectionState.fromNode?.id);
console.log('To node:', connectionState.toNode?.id);
console.log('From handle:', connectionState.fromHandle);
console.log('To handle:', connectionState.toHandle);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onConnectStart={onConnectStart}
onConnectEnd={onConnectEnd}
/>
);
}Validate Connections
import { ReactFlow, Connection, Edge, Node } from '@xyflow/react';
function ValidatedConnectionExample() {
const isValidConnection = (connection: Connection | Edge) => {
// Prevent self-connections
if (connection.source === connection.target) {
return false;
}
// Custom validation logic
const sourceNode = nodes.find(n => n.id === connection.source);
const targetNode = nodes.find(n => n.id === connection.target);
// Prevent connections from output nodes
if (sourceNode?.type === 'output') {
return false;
}
// Prevent connections to input nodes
if (targetNode?.type === 'input') {
return false;
}
return true;
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
isValidConnection={isValidConnection}
/>
);
}Selection Events
useOnSelectionChange Hook
import { useOnSelectionChange, OnSelectionChangeParams } from '@xyflow/react';
import { useCallback } from 'react';
function SelectionLogger() {
const onChange = useCallback(({ nodes, edges }: OnSelectionChangeParams) => {
console.log('Selected nodes:', nodes.map(n => n.id));
console.log('Selected edges:', edges.map(e => e.id));
}, []);
useOnSelectionChange({
onChange,
});
return null;
}
function SelectionExample() {
return (
<ReactFlow nodes={nodes} edges={edges}>
<SelectionLogger />
</ReactFlow>
);
}Selection Drag
import { ReactFlow, SelectionDragHandler } from '@xyflow/react';
function SelectionDragExample() {
const onSelectionDragStart: SelectionDragHandler = (event, nodes) => {
console.log('Selection drag started:', nodes.length, 'nodes');
};
const onSelectionDrag: SelectionDragHandler = (event, nodes) => {
console.log('Dragging selection:', nodes.map(n => n.id));
};
const onSelectionDragStop: SelectionDragHandler = (event, nodes) => {
console.log('Selection drag stopped');
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onSelectionDragStart={onSelectionDragStart}
onSelectionDrag={onSelectionDrag}
onSelectionDragStop={onSelectionDragStop}
/>
);
}Selection Context Menu
import { ReactFlow, Node, Edge } from '@xyflow/react';
function SelectionContextMenuExample() {
const onSelectionContextMenu = (event: React.MouseEvent, nodes: Node[]) => {
event.preventDefault();
console.log('Context menu on selection:', nodes.map(n => n.id));
// Show custom context menu
// ... context menu logic
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onSelectionContextMenu={onSelectionContextMenu}
/>
);
}Viewport Events
useOnViewportChange Hook
import { useOnViewportChange, Viewport } from '@xyflow/react';
import { useCallback } from 'react';
function ViewportLogger() {
const onStart = useCallback((viewport: Viewport) => {
console.log('Viewport change started:', viewport);
}, []);
const onChange = useCallback((viewport: Viewport) => {
console.log('Viewport:', {
x: viewport.x,
y: viewport.y,
zoom: viewport.zoom,
});
}, []);
const onEnd = useCallback((viewport: Viewport) => {
console.log('Viewport change ended:', viewport);
}, []);
useOnViewportChange({
onStart,
onChange,
onEnd,
});
return null;
}Move Events
import { ReactFlow, OnMove } from '@xyflow/react';
function MoveExample() {
const onMove: OnMove = (event, viewport) => {
console.log('Viewport moved to:', viewport);
};
const onMoveStart: OnMove = (event, viewport) => {
console.log('Move started from:', viewport);
};
const onMoveEnd: OnMove = (event, viewport) => {
console.log('Move ended at:', viewport);
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onMove={onMove}
onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd}
/>
);
}Pane Events
Click Events
import { ReactFlow } from '@xyflow/react';
import { MouseEvent } from 'react';
function PaneClickExample() {
const onPaneClick = (event: MouseEvent) => {
console.log('Pane clicked at:', event.clientX, event.clientY);
};
const onPaneContextMenu = (event: MouseEvent) => {
event.preventDefault();
console.log('Pane right-clicked');
};
const onPaneScroll = (event?: MouseEvent | WheelEvent) => {
console.log('Pane scrolled');
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onPaneClick={onPaneClick}
onPaneContextMenu={onPaneContextMenu}
onPaneScroll={onPaneScroll}
/>
);
}Mouse Events
import { ReactFlow } from '@xyflow/react';
import { MouseEvent } from 'react';
function PaneMouseExample() {
const onPaneMouseEnter = (event: MouseEvent) => {
console.log('Mouse entered pane');
};
const onPaneMouseMove = (event: MouseEvent) => {
console.log('Mouse moving over pane');
};
const onPaneMouseLeave = (event: MouseEvent) => {
console.log('Mouse left pane');
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onPaneMouseEnter={onPaneMouseEnter}
onPaneMouseMove={onPaneMouseMove}
onPaneMouseLeave={onPaneMouseLeave}
/>
);
}Init and Delete Events
Initialization
import { ReactFlow, OnInit, ReactFlowInstance } from '@xyflow/react';
function InitExample() {
const onInit: OnInit = (reactFlowInstance: ReactFlowInstance) => {
console.log('React Flow initialized');
console.log('Viewport:', reactFlowInstance.getViewport());
reactFlowInstance.fitView();
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onInit={onInit}
/>
);
}Delete Events
import { ReactFlow, OnNodesDelete, OnEdgesDelete, OnBeforeDelete } from '@xyflow/react';
function DeleteExample() {
const onNodesDelete: OnNodesDelete = (nodes) => {
console.log('Nodes deleted:', nodes.map(n => n.id));
};
const onEdgesDelete: OnEdgesDelete = (edges) => {
console.log('Edges deleted:', edges.map(e => e.id));
};
const onBeforeDelete: OnBeforeDelete = async ({ nodes, edges }) => {
console.log('About to delete:', nodes.length, 'nodes and', edges.length, 'edges');
// Return true to allow deletion, false to cancel
const confirmed = window.confirm('Delete selected elements?');
return confirmed;
};
const onDelete = ({ nodes, edges }) => {
console.log('Deleted:', nodes.length, 'nodes and', edges.length, 'edges');
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesDelete={onNodesDelete}
onEdgesDelete={onEdgesDelete}
onBeforeDelete={onBeforeDelete}
onDelete={onDelete}
/>
);
}Error Handling
import { ReactFlow, OnError } from '@xyflow/react';
function ErrorHandlingExample() {
const onError: OnError = (code, message) => {
console.error(`React Flow Error [${code}]:`, message);
// Handle specific error codes
if (code === '010') {
console.error('Handle must be rendered inside a custom node');
}
};
return (
<ReactFlow
nodes={nodes}
edges={edges}
onError={onError}
/>
);
}Viewport Control
React Flow provides viewport control through the useReactFlow() hook, which exposes methods for programmatic navigation, zoom, and coordinate transformations.
Table of Contents
- useReactFlow Hook
- fitView Method
- Zoom Methods
- setViewport Method
- setCenter Method
- screenToFlowPosition Method
- flowToScreenPosition Method
- Save and Restore Viewport State
- Programmatic Pan to Node
- Controlled Viewport
- useOnViewportChange Hook
- getNodesBounds Method
- viewportInitialized Flag
useReactFlow Hook
The main hook for accessing viewport and flow instance methods:
import { useReactFlow } from '@xyflow/react';
function ViewportControls() {
const reactFlow = useReactFlow();
// Access viewport methods
const handleZoomIn = () => reactFlow.zoomIn();
const handleFitView = () => reactFlow.fitView();
return (
<div>
<button onClick={handleZoomIn}>Zoom In</button>
<button onClick={handleFitView}>Fit View</button>
</div>
);
}fitView Method
Adjusts the viewport to fit all nodes in view:
import { useReactFlow, FitViewOptions } from '@xyflow/react';
function FitViewExample() {
const { fitView } = useReactFlow();
const handleFitView = async () => {
// Basic usage
await fitView();
// With options
await fitView({
padding: 0.2, // 20% padding around nodes
includeHiddenNodes: false, // Don't include hidden nodes
minZoom: 0.5, // Minimum zoom level
maxZoom: 2, // Maximum zoom level
duration: 200, // Animation duration in ms
});
};
return <button onClick={handleFitView}>Fit View</button>;
}fitView with Specific Nodes
Fit viewport to a subset of nodes:
import { useReactFlow } from '@xyflow/react';
function FitSpecificNodes() {
const { fitView, getNodes } = useReactFlow();
const fitSelectedNodes = async () => {
const selectedNodes = getNodes().filter(node => node.selected);
if (selectedNodes.length > 0) {
await fitView({
nodes: selectedNodes,
padding: 0.3,
duration: 400,
});
}
};
return <button onClick={fitSelectedNodes}>Fit Selected</button>;
}Zoom Methods
import { useReactFlow } from '@xyflow/react';
function ZoomControls() {
const { zoomIn, zoomOut, zoomTo, getZoom } = useReactFlow();
const handleZoomIn = () => {
zoomIn({ duration: 300 }); // Animated zoom
};
const handleZoomOut = () => {
zoomOut({ duration: 300 });
};
const handleZoomTo = () => {
zoomTo(1.5, { duration: 500 }); // Zoom to specific level
};
const handleGetZoom = () => {
const currentZoom = getZoom();
console.log('Current zoom:', currentZoom);
};
return (
<div>
<button onClick={handleZoomIn}>Zoom In</button>
<button onClick={handleZoomOut}>Zoom Out</button>
<button onClick={handleZoomTo}>Zoom to 1.5x</button>
<button onClick={handleGetZoom}>Get Zoom</button>
</div>
);
}setViewport Method
Directly set the viewport position and zoom:
import { useReactFlow, Viewport } from '@xyflow/react';
function ViewportSetter() {
const { setViewport, getViewport } = useReactFlow();
const handleSetViewport = () => {
const newViewport: Viewport = {
x: 100,
y: 100,
zoom: 1.2,
};
setViewport(newViewport, { duration: 400 });
};
const handleGetViewport = () => {
const viewport = getViewport();
console.log('Current viewport:', viewport);
// { x: 0, y: 0, zoom: 1 }
};
return (
<div>
<button onClick={handleSetViewport}>Set Viewport</button>
<button onClick={handleGetViewport}>Get Viewport</button>
</div>
);
}setCenter Method
Center the viewport on specific coordinates:
import { useReactFlow } from '@xyflow/react';
function CenterControls() {
const { setCenter } = useReactFlow();
const centerOnPosition = () => {
setCenter(
250, // x coordinate
250, // y coordinate
{
zoom: 1.5,
duration: 500,
}
);
};
return <button onClick={centerOnPosition}>Center on (250, 250)</button>;
}screenToFlowPosition Method
Convert screen coordinates to flow coordinates:
import { useReactFlow } from '@xyflow/react';
import { MouseEvent } from 'react';
function ClickToAddNode() {
const { screenToFlowPosition, setNodes } = useReactFlow();
const handlePaneClick = (event: MouseEvent) => {
// Convert click position to flow coordinates
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
// Add node at click position
setNodes((nodes) => [
...nodes,
{
id: `node-${Date.now()}`,
position,
data: { label: 'New Node' },
},
]);
};
return <ReactFlow onPaneClick={handlePaneClick} />;
}flowToScreenPosition Method
Convert flow coordinates to screen coordinates:
import { useReactFlow } from '@xyflow/react';
function PositionConverter() {
const { flowToScreenPosition } = useReactFlow();
const getScreenPosition = () => {
const screenPos = flowToScreenPosition({
x: 100,
y: 100,
});
console.log('Screen position:', screenPos);
};
return <button onClick={getScreenPosition}>Get Screen Position</button>;
}Save and Restore Viewport State
import { useState } from 'react';
import { useReactFlow, Viewport } from '@xyflow/react';
function ViewportPersistence() {
const { setViewport, getViewport } = useReactFlow();
const [savedViewport, setSavedViewport] = useState<Viewport | null>(null);
const saveViewport = () => {
const viewport = getViewport();
setSavedViewport(viewport);
// Optionally save to localStorage
localStorage.setItem('flowViewport', JSON.stringify(viewport));
};
const restoreViewport = () => {
if (savedViewport) {
setViewport(savedViewport, { duration: 300 });
} else {
// Load from localStorage
const stored = localStorage.getItem('flowViewport');
if (stored) {
const viewport = JSON.parse(stored) as Viewport;
setViewport(viewport, { duration: 300 });
}
}
};
return (
<div>
<button onClick={saveViewport}>Save Viewport</button>
<button onClick={restoreViewport}>Restore Viewport</button>
</div>
);
}Programmatic Pan to Node
Pan the viewport to focus on a specific node:
import { useReactFlow } from '@xyflow/react';
function PanToNode() {
const { getNode, setCenter } = useReactFlow();
const panToNodeById = (nodeId: string) => {
const node = getNode(nodeId);
if (node) {
const x = node.position.x + (node.width ?? 0) / 2;
const y = node.position.y + (node.height ?? 0) / 2;
setCenter(x, y, { zoom: 1.5, duration: 500 });
}
};
return (
<button onClick={() => panToNodeById('node-1')}>
Pan to Node 1
</button>
);
}Controlled Viewport
Control viewport directly through state:
import { useState, useCallback } from 'react';
import { ReactFlow, Viewport, useReactFlow } from '@xyflow/react';
function ControlledViewportFlow() {
const [viewport, setViewport] = useState<Viewport>({ x: 0, y: 0, zoom: 1 });
const { fitView } = useReactFlow();
const handleViewportChange = useCallback((newViewport: Viewport) => {
setViewport(newViewport);
}, []);
const updateViewport = () => {
setViewport((vp) => ({ ...vp, y: vp.y + 10 }));
};
return (
<>
<button onClick={updateViewport}>Move Down</button>
<button onClick={() => fitView()}>Fit View</button>
<ReactFlow
nodes={nodes}
edges={edges}
viewport={viewport}
onViewportChange={handleViewportChange}
/>
</>
);
}useOnViewportChange Hook
Listen to viewport changes:
import { useOnViewportChange, Viewport } from '@xyflow/react';
import { useCallback } from 'react';
function ViewportLogger() {
const onStart = useCallback((viewport: Viewport) => {
console.log('Viewport change started:', viewport);
}, []);
const onChange = useCallback((viewport: Viewport) => {
console.log('Viewport changing:', viewport);
}, []);
const onEnd = useCallback((viewport: Viewport) => {
console.log('Viewport change ended:', viewport);
}, []);
useOnViewportChange({
onStart,
onChange,
onEnd,
});
return null;
}getNodesBounds Method
Get bounding box of specific nodes:
import { useReactFlow } from '@xyflow/react';
function NodeBounds() {
const { getNodesBounds, getNodes } = useReactFlow();
const logSelectedBounds = () => {
const selectedNodes = getNodes().filter(n => n.selected);
const bounds = getNodesBounds(selectedNodes);
console.log('Bounds:', {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
});
};
return <button onClick={logSelectedBounds}>Log Selected Bounds</button>;
}viewportInitialized Flag
Check if viewport is initialized before using methods:
import { useReactFlow } from '@xyflow/react';
function SafeViewportControls() {
const { viewportInitialized, fitView } = useReactFlow();
const handleFitView = () => {
if (viewportInitialized) {
fitView();
} else {
console.warn('Viewport not yet initialized');
}
};
return (
<button onClick={handleFitView} disabled={!viewportInitialized}>
Fit View
</button>
);
}Related skills
FAQ
Which library does this skill cover?
React Flow for node-based graph editors and interactive canvases.
What customizations are supported?
Custom node and edge types with connection validation and layout helpers.
Which project is it scoped to?
The Beagle project architecture and conventions.
Is React Flow safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.