A powerful React hook for building interactive workflow diagrams with React Flow
npm install react-flow-workflowA powerful React hook for building interactive workflow diagrams with React Flow. The useWorkflowBuilder hook simplifies the creation and management of node-based workflows with automatic layout capabilities.



Check out the live demo application built with react-flow-workflow:
🚀 Pokemon Flow Visualizer
📁 GitHub Repository
!Demo
- Demo
- Installation
- Quick Start
- Features
- When to Use
- Basic Usage
- Working with Edges
- Creating Edges
- Custom Edge Styles
- Edge Analysis
- Advanced Usage
- Multiple Components
- Custom Node Types
- With ReactFlowProvider
- API Reference
- License
This package requires React 18+ and React Flow 11+ as peer dependencies.
``bashUsing npm
npm install react-flow-workflow reactflow
$3
`bash
Import the CSS in your main component or entry file
import 'reactflow/dist/style.css';
`Quick Start
Here's a minimal example to get you started:
`jsx
import React from 'react';
import ReactFlow from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';
import 'reactflow/dist/style.css';function SimpleWorkflow() {
const {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
createNode,
} = useWorkflowBuilder();
const addNode = () => {
createNode({
data: { label: 'New Node' },
});
};
return (
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
);
}export default SimpleWorkflow;
`Features
useWorkflowBuilder provides several advantages over using React Flow's standard functions:- 🔄 Smart Layout Engine - Built-in layout engine with horizontal (LR) and vertical (TB) flow support
- 📏 Configurable Spacing - Customizable horizontal and vertical spacing between nodes
- 🚫 Layout-First Architecture - All positioning handled by the layout engine, eliminating manual calculations
- 🧰 Simplified API - Combines multiple React Flow hooks into a single cohesive interface
- 🔍 Graph Analysis - Utilities for analyzing workflow structure (root nodes, leaf nodes, connections)
- ⚡ Enhanced Node & Edge Creation - Auto-generated IDs, consistent styling, and validation
- 🌐 Cross-Component State - Share workflow state across different components
- 🎯 Selection Management - Track selected nodes across your application
- 📋 Workflow-Specific Utilities - Methods designed specifically for workflow patterns
When to Use
$3
- Building node-based editors and workflow builders
- Applications where node layout needs to be automatically determined
- Projects requiring workflow analysis (finding start/end nodes, connections)
- UIs with multiple components that need to interact with the same workflow
- Applications where you want to reduce the boilerplate of working with React Flow
$3
- You need maximum control over every aspect of the implementation
- You have very specific or unusual layout requirements
- You're building a lightweight component with minimal workflow needs
- You're deeply familiar with React Flow and prefer working directly with its API
Basic Usage
`jsx
import React from 'react';
import ReactFlow, { Background, Controls } from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';
import 'reactflow/dist/style.css';const initialNodes = [
{
id: 'node-1',
position: { x: 0, y: 0 },
data: { label: 'Start' },
},
];
const WorkflowEditor = () => {
const {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
createNode,
} = useWorkflowBuilder({
initialNodes,
nodeWidth: 200,
nodeHeight: 80,
direction: 'LR', // Left to right flow (default)
autoLayout: false, // Manual layout by default
spacing: {
horizontal: 150, // Space between node columns
vertical: 120, // Space between nodes in same column
},
});
const handleAddNode = () => {
createNode({
data: { label: 'New Node' },
});
};
return (
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView>
);
};export default WorkflowEditor;
`Layout Configuration
The hook now provides better layout control with configurable spacing and direction:
$3
The package automatically pre-calculates node positions to eliminate the flicker effect and follows the workflow direction:
`jsx
// Nodes are positioned intelligently based on workflow direction
const workflow = useWorkflowBuilder({
direction: 'LR', // Horizontal flow
spacing: { horizontal: 80, vertical: 50 },
});// New nodes are automatically positioned following the workflow direction
const newNode = workflow.createNode({
data: { label: 'New Node' },
}); // No flicker - positioned to the right (LR) or below (TB)
// Position a node after a specific node in the workflow
const nextNode = workflow.createNodeInWorkflow(
{ data: { label: 'Next Step' } },
'previousNodeId' // Position after this node
);
// Or use the utility function for precise positioning
const positionedNode = workflow.createNodeAtPosition(
{ data: { label: 'Positioned Node' } },
'referenceNodeId',
{ x: 100, y: 0 } // Offset from reference node
);
`$3
The library now provides enhanced positioning options for better workflow visualization:
Standard Positioning:
`jsx
// Create nodes with standard workflow positioning
const newNode = workflow.createNode({
data: { label: 'New Node' },
}); // Automatically positioned following workflow direction// Position after a specific node
const nextNode = workflow.createNodeInWorkflow(
{ data: { label: 'Next Step' } },
'previousNodeId'
);
`Vertical Handler Positioning (New!):
`jsx
// For vertical workflows, position nodes with handlers at top or bottom
const topNode = workflow.createNodeWithVerticalHandlers(
{ data: { label: 'Top Handler' } },
'top' // Position above existing nodes
);const bottomNode = workflow.createNodeWithVerticalHandlers(
{ data: { label: 'Bottom Handler' } },
'bottom' // Position below existing nodes (default)
);
`Custom Positioning:
`jsx
// Position with custom offsets
const positionedNode = workflow.createNodeAtPosition(
{ data: { label: 'Positioned Node' } },
'referenceNodeId',
{ x: 100, y: 0 } // Offset from reference node
);
`$3
The library now provides better default spacing for clearer workflow visualization:
`jsx
const workflow = useWorkflowBuilder({
direction: 'LR', // Left to right
spacing: {
horizontal: 150, // Increased from 80 - more space between columns
vertical: 120, // Increased from 50 - more space between nodes
},
});
`Vertical Flow with Enhanced Spacing:
`jsx
const workflow = useWorkflowBuilder({
direction: 'TB', // Top to bottom
spacing: {
horizontal: 150, // More space between nodes in same row
vertical: 120, // More space between rows
},
});
`Benefits of Enhanced Spacing:
- Better Readability: More space between nodes makes workflows easier to follow
- Improved Handler Access: Top/bottom positioning for vertical workflows
- Professional Appearance: Cleaner, more organized workflow diagrams
- Flexible Layout: Easy to adjust spacing for different workflow densities
$3
`jsx
const workflow = useWorkflowBuilder({
direction: 'LR', // Left to right
spacing: {
horizontal: 150, // Space between columns
vertical: 120, // Space between nodes in same column
},
});
`$3
`jsx
const workflow = useWorkflowBuilder({
direction: 'TB', // Top to bottom
spacing: {
horizontal: 150, // Space between nodes in same row
vertical: 120, // Space between rows
},
});
`$3
`jsx
const workflow = useWorkflowBuilder({
spacing: {
horizontal: 200, // Wide spacing for complex workflows
vertical: 150, // Tall spacing for detailed nodes
},
});
`Layout-First Architecture
The hook now uses a layout-first approach where all node positioning is handled by the layout engine:
- 🎯 No Manual Positioning:
createNode, createNodeInWorkflow, and other functions no longer calculate positions
- 🔄 Automatic Layout: The useMemo layout calculation handles all positioning automatically
- 📊 Smart Positioning: Layout engine respects workflow direction, spacing, and special positioning requirements
- ⚡ Performance Optimized: Layout calculations are debounced and cached during drag operations$3
1. Node Creation: Functions like
createNode create nodes with temporary positions { x: 0, y: 0 }
2. Layout Calculation: The useMemo hook automatically recalculates layout when nodes/edges change
3. Position Assignment: The layout engine assigns final positions based on workflow direction and spacing
4. Auto-Centering: When autoCenter is enabled, the view centers on the last added node after layoutEnhanced Configuration Options
The hook now provides additional configuration options for better workflow visualization and performance:
`jsx
const workflow = useWorkflowBuilder({
// Basic configuration
direction: 'TB', // Top to bottom
spacing: {
horizontal: 150, // More space between nodes
vertical: 120, // More space between rows
}, // Required for auto-center functionality
useReactFlowInstance: true, // Must be true for auto-center to work
// Auto-view configuration
autoCenter: true, // Automatically center the workflow on the last added node
animate: true, // Smooth transitions for centering (enabled by default)
animationDuration: 300, // Customize animation duration in milliseconds
});
`$3
-
autoCenter: Centers the workflow view on the last added node when nodes are added/removed (uses React Flow's setCenter)
- animate: Enables smooth transitions (300ms duration by default) for centering⚠️ Important: Auto-center requires
useReactFlowInstance: true and your component to be wrapped with ReactFlowProvider.Working with Edges
$3
Creating edges programmatically between nodes:
`jsx
import React from 'react';
import ReactFlow from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';export default function EdgeExample() {
const {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
createNode,
createEdge,
} = useWorkflowBuilder();
const addNodesWithConnection = () => {
// Create two nodes
const sourceNode = createNode({
data: { label: 'Source' },
});
const targetNode = createNode({
data: { label: 'Target' },
});
// Connect them with an edge
createEdge({
source: sourceNode.id,
target: targetNode.id,
// Optional label
label: 'connects to',
});
};
return (
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
);
}
`$3
Customizing edge appearance:
`jsx
import React from 'react';
import ReactFlow, { EdgeTypes } from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';
import CustomEdge from './CustomEdge';export default function StyledEdgesExample() {
const {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
createNode,
createEdge,
getDefaultEdgeOptions,
} = useWorkflowBuilder();
// Define custom edge types
const edgeTypes: EdgeTypes = {
custom: CustomEdge,
};
const addCustomEdge = () => {
// Create two nodes
const sourceNode = createNode({
data: { label: 'Source' },
});
const targetNode = createNode({
data: { label: 'Target' },
});
// Create a styled edge
createEdge({
source: sourceNode.id,
target: targetNode.id,
type: 'custom', // Use the custom edge type
style: {
stroke: '#ff0072',
strokeWidth: 2,
},
animated: true,
label: 'Custom Edge',
});
};
const addDashedEdge = () => {
const sourceNode = createNode({
data: { label: 'Node A' },
});
const targetNode = createNode({
data: { label: 'Node B' },
});
// Create a dashed edge
createEdge({
source: sourceNode.id,
target: targetNode.id,
style: {
strokeDasharray: '5,5',
stroke: '#0041d0',
},
});
};
return (
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
edgeTypes={edgeTypes}
defaultEdgeOptions={getDefaultEdgeOptions()}
fitView
/>
);
}
`$3
Analyzing connections and workflow paths:
`jsx
import React, { useState } from 'react';
import ReactFlow from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';export default function EdgeAnalysisExample() {
const {
nodes,
edges,
onNodesChange,
onEdgesChange,
onConnect,
getNodeById,
getIncomingNodes,
getOutgoingNodes,
getRootNodes,
getLeafNodes,
} = useWorkflowBuilder({
initialNodes: [
{
id: 'a',
data: { label: 'Start' },
position: { x: 0, y: 0 },
},
{
id: 'b',
data: { label: 'Process' },
position: { x: 100, y: 100 },
},
{
id: 'c',
data: { label: 'End' },
position: { x: 200, y: 200 },
},
],
initialEdges: [
{ id: 'e1', source: 'a', target: 'b' },
{ id: 'e2', source: 'b', target: 'c' },
],
});
const [analysisResult, setAnalysisResult] = useState('');
const analyzeWorkflow = () => {
const rootNodes = getRootNodes();
const leafNodes = getLeafNodes();
const middleNodeId = 'b';
const incoming = getIncomingNodes(middleNodeId);
const outgoing = getOutgoingNodes(middleNodeId);
setAnalysisResult(
);
}; return (
{analysisResult}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
/>
);
}
`Advanced Usage
$3
The package uses the proven Dagre graph layout engine, which provides:
- Automatic node positioning with optimal spacing
- Smart edge routing to avoid overlaps
- Hierarchical layout for complex workflows
- Cycle handling for complex graph structures
- Configurable spacing for different workflow densities
The layout automatically handles both horizontal (LR) and vertical (TB) flows with proper spacing and edge routing.
$3
Using
useWorkflowBuilder across multiple components with ReactFlowProvider:`jsx
// App.tsx
import React from 'react';
import { ReactFlowProvider } from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';
import 'reactflow/dist/style.css';
import WorkflowEditor from './WorkflowEditor';
import Toolbar from './Toolbar';
import NodeInspector from './NodeInspector';export default function App() {
return (
);
}
// Toolbar.tsx with edge creation
import React from 'react';
import { useWorkflowBuilder } from 'react-flow-workflow';
export default function Toolbar() {
// Access the shared workflow state via ReactFlowProvider
const {
createNode,
createEdge,
getRootNodes,
getLeafNodes,
} = useWorkflowBuilder({
useReactFlowInstance: true
});
const addNode = () => {
createNode({
data: { label: 'New Node' }
});
};
const connectLastNodes = () => {
// Get the most recent leaf nodes
const sourceNodes = getRootNodes();
const targetNodes = getLeafNodes();
if (sourceNodes.length > 0 && targetNodes.length > 0) {
// Connect the last root to the first leaf that isn't the same node
const source = sourceNodes[sourceNodes.length - 1];
const target = targetNodes.find(node => node.id !== source.id);
if (target) {
createEdge({
source: source.id,
target: target.id,
label: 'Auto-connected'
});
}
}
};
return (
);
}
`$3
`jsx
// CustomNode.tsx
import React, { memo } from 'react';
import { Handle, Position } from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';const CustomNode = ({ id, data }) => {
const { selectedNodeId } = useWorkflowBuilder({
useReactFlowInstance: true,
});
const isSelected = id === selectedNodeId;
return (
style={{
padding: '10px',
borderRadius: '5px',
border: isSelected
? '2px solid #1a192b'
: '1px solid #ddd',
background: data.color || '#ffffff',
}}>
{data.label}
export default memo(CustomNode);
// Usage in main component
const nodeTypes = { custom: CustomNode };
// In your flow component:
edges={edges}
nodeTypes={nodeTypes}
// ... other props
/>;
`
`jsx
// WorkflowApp.tsx
import React from 'react';
import { ReactFlowProvider } from 'reactflow';
import { useWorkflowBuilder } from 'react-flow-workflow';
import WorkflowEditor from './WorkflowEditor';
import EdgeControls from './EdgeControls';
export default function WorkflowApp() {
return (
);
}
// EdgeControls.tsx - A component for edge operations
import React, { useState } from 'react';
import { useWorkflowBuilder } from 'react-flow-workflow';
export default function EdgeControls() {
const {
nodes,
edges,
createEdge,
fitView
} = useWorkflowBuilder({
useReactFlowInstance: true
});
const [source, setSource] = useState('');
const [target, setTarget] = useState('');
const handleCreateEdge = () => {
if (source && target) {
createEdge({
source,
target,
label: ${source} → ${target},
animated: true
});
fitView();
}
};
return (
value={target}
onChange={(e) => setTarget(e.target.value)}
>
{nodes.map(node => (
))}
onClick={handleCreateEdge}
disabled={!source || !target}
>
Connect Nodes
API Reference
$3
`typescript
interface UseWorkFlowBuilderProps {
nodeWidth?: number; // Default: 200
nodeHeight?: number; // Default: 80
direction?: 'TB' | 'LR'; // Default: 'LR' (left to right)
initialNodes?: Node[]; // Default: []
initialEdges?: Edge[]; // Default: []
autoLayout?: boolean; // Default: false
useReactFlowInstance?: boolean; // Default: false
spacing?: {
horizontal?: number; // Default: 150
vertical?: number; // Default: 120
};
}
`$3
`typescript
// Core React Flow properties
nodes: Node[];
edges: Edge[];
onNodesChange: OnNodesChange;
onEdgesChange: OnEdgesChange;
onConnect: OnConnect;// Layout functionality
// Node operations
createNode: (nodeData: Partial) => Node;
createNodeAtPosition: (nodeData: Partial, relativeTo?: string, offset?: { x: number; y: number }) => Node;
createNodeInWorkflow: (nodeData: Partial, afterNodeId?: string) => Node;
createNodeWithVerticalHandlers: (nodeData: Partial, handlerPosition?: 'top' | 'bottom') => Node;
updateNodeById: (nodeId: string, updates: Partial) => void;
deleteNode: (nodeId: string) => void;
getNodeById: (nodeId: string) => Node | undefined;
// Edge operations
createEdge: (edgeData: Partial) => Edge | null;
getDefaultEdgeOptions: () => object;
// Selection operations
selectedNodeId: string | null;
setSelectedNodeId: (id: string | null) => void;
// Graph analysis
getOutgoingNodes: (nodeId: string) => Node[];
getIncomingNodes: (nodeId: string) => Node[];
getRootNodes: () => Node[];
getLeafNodes: () => Node[];
// React Flow instance (only available if useReactFlowInstance is true)
reactFlowInstance: ReactFlowInstance | null;
fitView: () => boolean;
// Utility
resetCounters: () => void;
`Package Information
- Package Name:
react-flow-workflow
- Version: 0.5.0
- License: MIT
- Repository: GitHub
- Issues: GitHub Issues$3
-
react: ^18.0.0
- react-dom: ^18.0.0
- reactflow`: ^11.0.0MIT