Skip to content

Brian-Mwangi-developer/Learn-ReactXyflow

Repository files navigation

Workflow Builder with ReactFlow (XYFlow)

A visual workflow builder built with Next.js and ReactFlow that allows you to create, edit, and execute data processing workflows through an intuitive drag-and-drop interface.

πŸ“‹ Table of Contents

🎯 Overview

This project is a visual workflow builder that demonstrates how to use ReactFlow (XYFlow) to create node-based workflows. Users can:

  • Drag nodes from a sidebar onto a canvas
  • Connect nodes to define data flow
  • Configure each node with specific parameters
  • Execute workflows and see real-time results
  • Save and load workflows from a database

What is ReactFlow/XYFlow?
ReactFlow is a library for building node-based editors and interactive diagrams. It provides:

  • Canvas with pan, zoom, and minimap
  • Custom node and edge rendering
  • Connection handling and validation
  • Built-in controls and utilities

πŸ›  Key Technologies

Technology Purpose Version
Next.js React framework with App Router 16.x
ReactFlow (@xyflow/react) Node-based canvas and workflow visualization 12.x
Zustand Lightweight state management 5.x
Zod Runtime schema validation 4.x
Tailwind CSS Styling framework 4.x
Prisma Database ORM 7.x
shadcn/ui UI component library Latest

πŸš€ Getting Started

Prerequisites

  • Node.js 18+ or Bun
  • PostgreSQL (for workflow persistence)

Installation

  1. Clone and install dependencies:

    bun install
    # or npm install
  2. Set up environment variables:

    cp .env.example .env
    # Edit .env with your database URL
  3. Run database migrations:

    bunx prisma migrate dev
  4. Start the development server:

    bun dev
    # or npm run dev
  5. Open http://localhost:3000

πŸ“ Project Structure

src/
β”œβ”€β”€ app/                          # Next.js App Router
β”‚   β”œβ”€β”€ page.tsx                  # Main workflow builder page
β”‚   └── api/workflows/            # API routes for workflow CRUD
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/                       # shadcn UI components
β”‚   └── workflow/                 # Workflow builder components
β”‚       β”œβ”€β”€ index.tsx             # Main WorkflowBuilder component
β”‚       β”œβ”€β”€ canvas.tsx            # ReactFlow canvas (drag/drop target)
β”‚       β”œβ”€β”€ nodes.tsx             # Custom node components
β”‚       β”œβ”€β”€ node-drawer.tsx       # Sidebar with draggable nodes
β”‚       β”œβ”€β”€ node-editor.tsx       # Right panel for editing nodes
β”‚       └── toolbar.tsx           # Top toolbar with actions
└── lib/
    └── workflow/
        β”œβ”€β”€ types.ts              # TypeScript types & Zod schemas
        β”œβ”€β”€ store.ts              # Zustand state management
        β”œβ”€β”€ executor.worker.ts    # Web Worker for execution
        β”œβ”€β”€ use-executor.ts       # Hook to run workflows
        └── sample-workflows.ts   # Pre-built example workflows

🧩 Core Concepts

ReactFlow Fundamentals

1. Nodes

Nodes are the building blocks of a workflow. Each node has:

  • id: Unique identifier
  • type: Determines appearance and behavior
  • position: { x, y } coordinates on canvas
  • data: Custom data specific to the node type
const node: WorkflowNode = {
  id: "node_1",
  type: "httpRequest",
  position: { x: 100, y: 200 },
  data: {
    nodeType: "httpRequest",
    label: "Fetch API",
    url: "https://api.example.com/data",
    method: "GET",
  },
};

2. Edges

Edges define connections between nodes (data flow). Each edge has:

  • source: ID of the starting node
  • target: ID of the destination node
  • sourceHandle: Which output on the source node
  • targetHandle: Which input on the target node
  • type: Visual style ("smoothstep", "step", "straight", "bezier")
const edge: WorkflowEdge = {
  id: "edge_1",
  source: "node_1",
  target: "node_2",
  sourceHandle: "output",
  targetHandle: "input",
  type: "step", // Right-angle connections
};

3. Node Types Registration

Custom node components must be registered with ReactFlow:

const nodeTypes = {
  start: StartNode,
  httpRequest: HttpRequestNode,
  transform: TransformNode,
  // ... other node types
};

<ReactFlow nodeTypes={nodeTypes} ... />

4. Handles

Handles are connection points on nodes:

  • Source handles: Where edges start (outputs)
  • Target handles: Where edges end (inputs)
  • Positioned using Position.Top/Right/Bottom/Left
<Handle
  type="source"
  position={Position.Right}
  id="output"
/>

🎨 Node Types

Node Type Icon Purpose Key Properties
Start ▢️ Workflow entry point inputData - Initial data
HTTP Request 🌐 API calls method, url, headers, body
Transform πŸ’» Data transformation expression - JavaScript code
Condition πŸ”€ Conditional branching condition - Boolean expression
Delay ⏰ Pause execution duration - Milliseconds
Output πŸ“„ Display results format - "json" or "text"
Merge πŸ”„ Combine multiple inputs strategy - How to merge
Set Variable πŸ“ Store data variableName, value

Node Handles Configuration

  • Start, HTTP, Transform, Delay, Output: 1 input (left), 1 output (right)
  • Condition: 1 input (left), 2 outputs (right) - "true" (green) and "false" (red)
  • Merge: Multiple inputs (left), 1 output (right)

πŸ”— Edge Types

ReactFlow supports multiple edge visual styles:

Type Appearance Use Case
smoothstep Smooth curved lines Default, clean look
step Right-angle (90Β°) lines Technical diagrams
straight Direct lines Simple connections
bezier Classic curves Similar to smoothstep

Change edge type in workflow definitions:

edges: [
  {
    id: "edge_1",
    source: "start",
    target: "http",
    type: "step", // Try: "smoothstep", "straight", "bezier"
  },
];

πŸ–± Drag & Drop Implementation

How It Works

The drag-and-drop system uses the HTML5 Drag & Drop API:

1. Making Nodes Draggable (node-drawer.tsx)

const handleDragStart = (event: DragEvent) => {
  // Store node type in dataTransfer
  event.dataTransfer.setData(
    "application/reactflow-nodetype",
    nodeType
  );
  event.dataTransfer.effectAllowed = "move";
};

<div draggable onDragStart={handleDragStart}>
  {/* Node tile */}
</div>

Key Point: "application/reactflow-nodetype" is a custom MIME type (just a string key) used to identify what's being dragged. It's NOT a TypeScript type or a pre-defined standardβ€”you can use any unique string.

2. Drop Target Setup (canvas.tsx)

const handleDragOver = (event: DragEvent) => {
  event.preventDefault(); // Required to allow dropping
  event.dataTransfer.dropEffect = "move";
};

const handleDrop = (event: DragEvent) => {
  event.preventDefault();

  // Retrieve the node type
  const nodeType = event.dataTransfer.getData("application/reactflow-nodetype");

  // Convert screen position to flow position
  const position = screenToFlowPosition({
    x: event.clientX,
    y: event.clientY,
  });

  // Create new node
  addNode(nodeType, position);
};

3. Non-Modal Sheet for Drawer

Problem: By default, shadcn's Sheet component uses a modal overlay that blocks the canvas, preventing drops.

Solution: Use modal={false} and showOverlay={false}:

<Sheet modal={false}>
  <SheetContent showOverlay={false}>
    {/* Draggable nodes */}
  </SheetContent>
</Sheet>

This allows the drawer to stay open while you drag-and-drop onto the canvas!

πŸ—‚ State Management

Zustand Store (store.ts)

The workflow state is managed with Zustand, a simple and efficient state library:

Key State:

interface WorkflowState {
  // Workflow data
  nodes: WorkflowNode[];
  edges: WorkflowEdge[];
  workflowName: string;
  workflowDescription: string;

  // UI state
  isDrawerOpen: boolean;
  selectedNodeId: string | null;

  // Execution state
  executionStatus: ExecutionStatus;
  nodeResults: Map<string, NodeExecutionResult>;

  // Actions
  addNode: (nodeType, position) => void;
  updateNode: (nodeId, data) => void;
  deleteNode: (nodeId) => void;
  // ... more actions
}

Using the Store:

function MyComponent() {
  const nodes = useWorkflowStore((state) => state.nodes);
  const addNode = useWorkflowStore((state) => state.addNode);

  // Use state and actions...
}

ReactFlow Providers

ReactFlow requires two providers:

<ReactFlowProvider>
  <TooltipProvider>
    <WorkflowBuilder />
  </TooltipProvider>
</ReactFlowProvider>
  • ReactFlowProvider: Enables useReactFlow() hook
  • TooltipProvider: For shadcn tooltips (optional)

βš™οΈ Workflow Execution

Execution Flow

  1. User clicks "Run" β†’ Store dispatches executeWorkflow()
  2. State transitions: idle β†’ running
  3. Web Worker spawned (keeps UI responsive)
  4. Nodes executed in order (topological sort)
  5. Results stored in nodeResults Map
  6. UI updates in real-time as each node completes

Validation

Before execution, workflows are validated:

  • βœ… Must have exactly one Start node
  • βœ… All nodes must be reachable from Start
  • βœ… No cycles (infinite loops)
  • βœ… All required node properties filled

Node Data Access

During execution, each node receives data from its inputs:

// In a Transform node expression:
const result = data.email.toLowerCase();
// 'data' = output from previous node

For Condition nodes:

condition: "data.age >= 18";
// Evaluates to true or false β†’ routes to correct output

πŸ’‘ Important Implementation Details

1. ReactFlow Must Be Inside Providers

// βœ… Correct
<ReactFlowProvider>
  <MyComponent />  {/* Can use useReactFlow() */}
</ReactFlowProvider>

// ❌ Wrong - useReactFlow() will fail
<MyComponent />

2. Custom Node Components Receive Props

type CustomNodeProps = {
  id: string;
  data: YourNodeData;
  selected: boolean;
};

function CustomNode({ id, data, selected }: CustomNodeProps) {
  // Your node UI
}

3. Handle IDs Must Match

// Node 1 (source)
<Handle type="source" id="output" />

// Node 2 (target)
<Handle type="target" id="input" />

// Edge connecting them
{ sourceHandle: "output", targetHandle: "input" }

4. Position Conversion for Drops

Always convert screen coordinates to flow coordinates:

const position = screenToFlowPosition({
  x: event.clientX,
  y: event.clientY,
});

This accounts for canvas pan/zoom.

5. Zod for Runtime Validation

All node data is validated with Zod schemas:

const result = HttpRequestNodeDataSchema.safeParse(data);
if (!result.success) {
  console.error(result.error);
}

6. Immer for Immutable Updates

Zustand uses Immer middleware for clean state updates:

// Without Immer (complex)
set((state) => ({
  nodes: state.nodes.map((n) =>
    n.id === id ? { ...n, data: { ...n.data, ...newData } } : n,
  ),
}));

// With Immer (simple)
set((state) => {
  const node = state.nodes.find((n) => n.id === id);
  if (node) node.data = { ...node.data, ...newData };
});

πŸ“š Learn More

ReactFlow Resources

Next.js Resources

🚒 Deployment

The easiest way to deploy is using Vercel:

vercel deploy

Make sure to:

  1. Set up PostgreSQL database
  2. Configure environment variables
  3. Run migrations: bunx prisma migrate deploy

Built with ❀️ using ReactFlow, Next.js, and Zustand

About

Learning React Xyflow with Ai Assistance , basics of Node,edges and How to make a Runnable Flow

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors