
As a software developer when I first started building complex dashboards and workflow tools, one of the biggest headaches was visualizing how everything connected. I tried several libraries from D3.js to flowcharts, but it quickly felt like trying to drive a race car without a powerful steering wheel, but hard to control. Then I came across React Flow, and honestly, it just clicked.
One project that stands out was a scheduling email system web app for a manufacturing team. They needed an easy way to design and tweak their production workflows. With React Flow, I was able to build a drag and drop interface that managers could understand in seconds. It saved them hours of manual charting, and saved me from writing spaghetti code.
In this guide, I’ll walk you through how I used React Flow, step-by-step. If you’re just starting out, don’t worry, I’ll keep it practical and beginner-friendly, with real examples from projects I’ve worked on.
Why Choose React Flow?
React Flow is a React library for building node-based interfaces like flow charts or diagrams. It’s ideal for visualizing workflows, data pipelines, or mind maps. Key features include:
Drag-and-Drop: Easily move nodes and connect them.
Zoom/Pan: Smooth navigation for large diagrams.
Customizable: Create tailored nodes and edges.
Performance: Renders only visible nodes for speed.
In my scheduling app, React Flow helped me create an intuitive interface for managers to design production workflows, saving hours compared to manual charting.
Step 1: Installation
Start with a React project (e.g., using Vite or Create React App). Install React Flow via npm:
import { ReactFlow } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
Import the component and styles:
import { ReactFlow } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
Ensure Node.js and a package manager are installed. In my projects, I use npm for consistency across teams.
Step 2: Creating a Basic Flow
React Flow uses nodes (elements) and edges (connections). Here’s a simple flowchart with two nodes and an edge:
import React, { useCallback } from 'react';
import { ReactFlow, useNodesState, useEdgesState, addEdge } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes = [
{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Start Process' } },
{ id: '2', position: { x: 0, y: 100 }, data: { label: 'End Process' } },
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
</div>
);
}
export default Flow;
Explanation:
Nodes: Define id, position, and data (e.g., labels).
Edges: Link nodes via source and target IDs.
Hooks: useNodesState and useEdgesState manage state.
Container: Must have defined dimensions (here, full-screen).
In my workflow app, I used similar code to let users map tasks like “Assembly” to “Quality Check,” making it easy to visualize dependencies.
Step 3: Adding Interactivity
React Flow’s built-in features shine here:
Dragging: Move nodes effortlessly.
Zooming/Panning: Navigate large flows with mouse or touch.
Selection: Click or Shift-select nodes/edges.
Shortcuts: Delete nodes with Delete or cancel with Escape.
Enhance the UI with components like MiniMap, Controls, and Background:
import { ReactFlow, MiniMap, Controls, Background } from '@xyflow/react';
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
>
<MiniMap />
<Controls />
<Background variant="dots" gap={12} size={1} />
</ReactFlow>
</div>
);
}
In my project, the MiniMap was a hit with users, letting them navigate complex workflows without losing context.
Step 4: Customizing Nodes
Custom nodes are React components, perfect for tailored UIs. Here’s a custom node with connection handles:
import { Handle, Position } from '@xyflow/react';
function CustomNode({ data }) {
return (
<div style={{ padding: 10, background: '#fff', border: '1px solid #000', borderRadius: 3 }}>
<Handle type="target" position={Position.Top} />
<div>{data.label}</div>
<Handle type="source" position={Position.Bottom} />
</div>
);
}
const nodeTypes = { custom: CustomNode };
function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState([
{ id: '1', type: 'custom', position: { x: 0, y: 0 }, data: { label: 'Custom Task' } },
]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes}
/>
</div>
);
}
I used custom nodes to display task statuses (e.g., “In Progress” with a green border), which made the workflow visually intuitive.
Step 5: State Management
For larger apps, I recommend Zustand for state management, as it’s lightweight and integrates well with React Flow. Here’s an example:
import create from 'zustand';
import { useShallow } from 'zustand/react/shallow';
import { applyNodeChanges, applyEdgeChanges, addEdge } from '@xyflow/react';
const useStore = create((set) => ({
nodes: initialNodes,
edges: initialEdges,
onNodesChange: (changes) => set((state) => ({ nodes: applyNodeChanges(changes, state.nodes) })),
onEdgesChange: (changes) => set((state) => ({ edges: applyEdgeChanges(changes, state.edges) })),
onConnect: (params) => set((state) => ({ edges: addEdge(params, state.edges) })),
}));
function Flow() {
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStore(
useShallow((state) => ({
nodes: state.nodes,
edges: state.edges,
onNodesChange: state.onNodesChange,
onEdgesChange: state.onEdgesChange,
onConnect: state.onConnect,
}))
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
);
}
In my app, Zustand helped manage complex workflows with dozens of nodes, keeping the UI responsive. This aligns with my experience using Zustand in other Next.js projects for clean state handling.
Step 6: Troubleshooting Tips
From my projects, here are common issues and fixes:
Styling: Ensure the container has a defined size (e.g., 100vw, 100vh).
State Access: Use for hooks like useReactFlow.
Performance: Wrap event handlers in useCallback to avoid re-renders.
Webpack: For Webpack 4, add Babel plugins; Webpack 5 works seamlessly.
Once, I hit a rendering issue due to missing container dimensions, which caused nodes to overlap. Setting explicit dimensions fixed it instantly.
Conclusion
There’s a reason I keep coming back to React Flow, it just makes building interactive, node-based interfaces feel intuitive. Whether it’s visualizing a data pipeline or mapping a manufacturing process, the flexibility it offers is hard to beat.
For me, the real win was giving non-technical users a tool they could actually use and enjoy using. That’s where the drag-and-drop interactions, customizable nodes, and real-time updates really shine.
If you’re just starting out, try building something small, tweak a few nodes, and don’t be afraid to break things (that’s how I learned).