Claude
Skills
Sign in
Back

react-flow

Included with Lifetime
$97 forever

Setup react-flow for node-based diagrams and flowcharts. Use this skill when the user says "setup react-flow", "add flow diagram", "node editor", "flowchart builder", or "visual workflow".

Web Dev

What this skill does


# React Flow Skill

Node-based diagram and flowchart library for React 19 and Next.js App Router.

## Installation

```bash
bun add @xyflow/react
```

## Next.js App Router Setup

React Flow requires client-side rendering. Create a wrapper component.

### 1. Flow Provider Wrapper

```tsx
// components/flow/flow-provider.tsx
"use client";

import { ReactFlowProvider } from "@xyflow/react";
import type { ReactNode } from "react";

type FlowProviderProps = {
  children: ReactNode;
};

export function FlowProvider({ children }: FlowProviderProps) {
  return <ReactFlowProvider>{children}</ReactFlowProvider>;
}
```

### 2. Basic Flow Component

```tsx
// components/flow/basic-flow.tsx
"use client";

import { useEffect, useState, useCallback, useMemo } from "react";
import {
  ReactFlow,
  Controls,
  Background,
  MiniMap,
  applyNodeChanges,
  applyEdgeChanges,
  addEdge,
  useReactFlow,
  type Node,
  type Edge,
  type NodeChange,
  type EdgeChange,
  type Connection,
  type OnConnect,
  type NodeTypes,
  type EdgeTypes,
} from "@xyflow/react";
import { useTheme } from "next-themes";
import "@xyflow/react/dist/style.css";

type BasicFlowProps = {
  initialNodes?: Node[];
  initialEdges?: Edge[];
  nodeTypes?: NodeTypes;
  edgeTypes?: EdgeTypes;
};

const defaultNodes: Node[] = [
  {
    id: "node-1",
    position: { x: 0, y: 0 },
    data: { label: "Start" },
  },
  {
    id: "node-2",
    position: { x: 0, y: 150 },
    data: { label: "Process" },
  },
  {
    id: "node-3",
    position: { x: 0, y: 300 },
    data: { label: "End" },
  },
];

const defaultEdges: Edge[] = [
  { id: "edge-1-2", source: "node-1", target: "node-2" },
  { id: "edge-2-3", source: "node-2", target: "node-3" },
];

export function BasicFlow({
  initialNodes = defaultNodes,
  initialEdges = defaultEdges,
  nodeTypes,
  edgeTypes,
}: BasicFlowProps) {
  const [nodes, setNodes] = useState<Node[]>(initialNodes);
  const [edges, setEdges] = useState<Edge[]>(initialEdges);

  // Dark mode: sync with next-themes (mounted guard prevents SSR mismatch)
  const { resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);

  // Re-center flow on window resize
  const { fitView } = useReactFlow();
  useEffect(() => {
    let rafId: number;
    const handleResize = () => {
      cancelAnimationFrame(rafId);
      rafId = requestAnimationFrame(() => {
        fitView({ padding: 0.4 });
      });
    };
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
      cancelAnimationFrame(rafId);
    };
  }, [fitView]);

  const onNodesChange = useCallback(
    (changes: NodeChange[]) =>
      setNodes((nds) => applyNodeChanges(changes, nds)),
    []
  );

  const onEdgesChange = useCallback(
    (changes: EdgeChange[]) =>
      setEdges((eds) => applyEdgeChanges(changes, eds)),
    []
  );

  const onConnect: OnConnect = useCallback(
    (connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
    []
  );

  const defaultEdgeOptions = useMemo(
    () => ({
      animated: true,
      type: "smoothstep",
    }),
    []
  );

  return (
    <div className="h-[600px] w-full">
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        nodeTypes={nodeTypes}
        edgeTypes={edgeTypes}
        defaultEdgeOptions={defaultEdgeOptions}
        fitView
        fitViewOptions={{ padding: 0.4, maxZoom: 1.2 }}
        proOptions={{ hideAttribution: true }}
        colorMode={mounted && resolvedTheme === "dark" ? "dark" : mounted && resolvedTheme === "light" ? "light" : "dark"}
      >
        <Controls />
        <MiniMap className="hidden md:block" />
        <Background gap={16} size={1} />
      </ReactFlow>
    </div>
  );
}
```

### 3. Usage in Page

```tsx
// app/flow/page.tsx
import { FlowProvider } from "@/components/flow/flow-provider";
import { BasicFlow } from "@/components/flow/basic-flow";

export default function FlowPage() {
  return (
    <FlowProvider>
      <div className="p-4">
        <h1 className="text-2xl font-bold mb-4">Flow Editor</h1>
        <BasicFlow />
      </div>
    </FlowProvider>
  );
}
```

## Core Concepts

### Nodes

Nodes are the building blocks of a flow. Each node requires:

- `id`: Unique identifier (string)
- `position`: `{ x: number, y: number }` coordinates
- `data`: Object containing node data (e.g., `{ label: "My Node" }`)
- `type`: Optional node type (default, input, output, or custom)

```tsx
const nodes: Node[] = [
  {
    id: "1",
    type: "input",
    position: { x: 100, y: 100 },
    data: { label: "Input Node" },
  },
  {
    id: "2",
    type: "default",
    position: { x: 100, y: 200 },
    data: { label: "Default Node" },
  },
  {
    id: "3",
    type: "output",
    position: { x: 100, y: 300 },
    data: { label: "Output Node" },
  },
];
```

### Edges

Edges connect nodes together:

- `id`: Unique identifier
- `source`: Source node ID
- `target`: Target node ID
- `sourceHandle`: Optional handle ID on source
- `targetHandle`: Optional handle ID on target
- `type`: Edge type (default, straight, step, smoothstep, bezier)
- `animated`: Boolean for animation
- `label`: Optional edge label

```tsx
const edges: Edge[] = [
  {
    id: "e1-2",
    source: "1",
    target: "2",
    type: "smoothstep",
    animated: true,
    label: "connects to",
  },
];
```

### Handles

Handles are connection points on nodes:

```tsx
import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";

type LabelNodeData = { label: string };
type LabelNode = Node<LabelNodeData>;

function CustomNode({ data }: NodeProps<LabelNode>) {
  return (
    <div className="px-4 py-2 bg-white border rounded shadow">
      <Handle type="target" position={Position.Top} />
      <div>{data.label}</div>
      <Handle type="source" position={Position.Bottom} />
    </div>
  );
}
```

**Important**: Always provide a generic type parameter to `NodeProps`. The default `NodeProps` types `data` as `Record<string, unknown>`, so `data.label` will be `unknown` and cause TypeScript errors when rendered in JSX.

Handle positions: `Position.Top`, `Position.Right`, `Position.Bottom`, `Position.Left`

### Controls

Built-in controls for zoom and fit:

```tsx
import { Controls } from "@xyflow/react";

<ReactFlow nodes={nodes} edges={edges}>
  <Controls />
</ReactFlow>;
```

## Custom Nodes

Define custom nodes outside the component to prevent re-renders.

```tsx
// components/flow/custom-nodes/card-node.tsx
"use client";

import { memo } from "react";
import { Handle, Position, type NodeProps, type Node } from "@xyflow/react";

type CardNodeData = {
  title: string;
  description: string;
  status: "pending" | "active" | "complete";
};

type CardNode = Node<CardNodeData, "card">;

function CardNodeComponent({ data, selected }: NodeProps<CardNode>) {
  const statusColors = {
    pending: "bg-yellow-100 border-yellow-400",
    active: "bg-blue-100 border-blue-400",
    complete: "bg-green-100 border-green-400",
  };

  return (
    <div
      className={`
        px-4 py-3 rounded-lg border-2 min-w-[200px]
        ${statusColors[data.status]}
        ${selected ? "ring-2 ring-blue-500" : ""}
      `}
    >
      <Handle type="target" position={Position.Top} className="!bg-gray-500" />
      <div className="font-semibold text-sm">{data.title}</div>
      <div className="text-xs text-gray-600 mt-1">{data.description}</div>
      <div className="text-xs text-gray-400 mt-2 capitalize">{data.status}</div>
      <Handle
        type="source"
        position={Position.Bottom}
        className="!bg-gray-500"
      />
    </div>
  );
}

export const CardNode = memo(CardNodeComponent);
```

### Registering Custom Nodes

```tsx
// components/flow/node-types.ts
import type { NodeTypes } from "@xyflow/react";
import { CardNode } from "./custo
Files: 1
Size: 20.0 KB
Complexity: 29/100
Category: Web Dev

Related in Web Dev