Introduction
Reactflow is a JavaScript library that allows developers to create interactive, node-based user interfaces. It is built on top of the popular React library and is designed to be easy to use and customize.
With Reactflow, developers can create nodes that represent different pieces of data or functionality, and connect them together with edges to create a flow of data or actions. This makes it easy to build interactive applications that allow users to manipulate data or perform tasks in a visual way.
Applications
Workflow builder - Onesignal
Interactive Process documentation - Stripe
Visualizing complex logic - Typeform
Orchestrating functions and integrations - Baseten
Dependency visualizer - Bit
Building a Flow
Creating Nodes
To create a node in Reactflow, you will need to import the Node component from the Reactflow library and use it in your React component. The Node component takes several props, including a label prop that is used to display text on the node and a data prop that can be used to store any data that you want to associate with the node.
For example, you could create a node like this:
import React from "react";
import ReactFlow, { Controls, Background } from "reactflow";
import "reactflow/dist/style.css";
const nodes = [
{
id: "1",
position: { x: 0, y: 0 },
data: { label: "Node" },
},
];
function Node() {
return (
<div style={{ height: "100%" }}>
<ReactFlow nodes={nodes}>
<Background />
<Controls />
</ReactFlow>
</div>
);
}
export default Node;

This would create a node with the label "Flow". You can customize the appearance of the node by using the various style props that are available, such as color, borderColor, and borderWidth. You would have noticed that the node is placed at (0,0).
More nodes can be added by either increasing the initial nodes provided or pushing node objects into the node array.
Assigning Edges
To connect nodes together and create a flow of data or actions, you can use the Edge component from the Reactflow library. The Edge component takes two props: source and target, which are used to specify the nodes that the edge should connect.
For example, you could create an edge like this:
import ReactFlow, { Controls, Background } from 'reactflow';
import 'reactflow/dist/style.css';
const edges = [{ id: '1-2', source: '1', target: '2' }];
const nodes = [
{
id: '1',
data: { label: 'Hello' },
position: { x: 0, y: 0 },
type: 'input',
},
{
id: '2',
data: { label: 'World' },
position: { x: 100, y: 100 },
},
];
function Flow() {
return (
<div style={{ height: '100%' }}>
<ReactFlow nodes={nodes} edges={edges}>
<Background />
<Controls />
</ReactFlow>
</div>
);
}
export default Flow;

This would create an edge that connects the node with the ID "1" to the node with the ID "2". You can customize the appearance of the edge by using the various style props that are available, such as color and width.
Now, the flow has been created successfully.
Adding Interactivity
Reactflow provides several props that you can use to add interactivity to your node-based user interfaces. For example, you can use the onNodesChange prop to specify a function that should be called when any action is taken on a node.. This function can be used to display additional information about the node or to perform some other action.
You can also use the onEdgesChange prop to specify a function that should be called when any action is taken on an edge. This function can be used to display additional information about the edge or to perform some other action.
const onNodesChange = useCallback(
(changes: NodeChange[]) =>
setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes]
);
const onEdgesChange = useCallback(
(changes: EdgeChange[]) =>
setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges]
);
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange} // nodeChanges here
onEdgesChange={onEdgesChange} // edgeChanges here
>
Creating Custom Nodes
One of the key features of Reactflow is the ability to create custom nodes that represent different pieces of data or functionality. To create a custom node, you will need to use the Node component and provide your own content inside the node.
For example, you could create a custom node like this:
import { useCallback } from "react";
const handleStyle = { left: 10 };
function CustomNodeTest({ data }: any) {
const onChange = useCallback((evt: any) => {
console.log(evt.target.value);
}, []);
return (
<div className="p-2 rounded border border-slate-800">
<label htmlFor="text">Custom Node with text input: </label>
<input id="text" name="text" onChange={onChange} />
</div>
);
}
export default CustomNodeTest;
Then import this node in our main file and include this in the nodes array with its type attribute as the function/component name.
import CustomNodeTest from "./myCustomNode";
const nodeTypes = useMemo(() => ({ customNode: CustomNodeTest }), []);
const nodes = [
{
id: "node-1",
type: "MyCustomNode",
position: { x: 0, y: 0 },
data: { value: 123 },
},
];
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes} // nodeTypes here
>

NodeTypes are memoized or it should be outside the component definition. Otherwise there would be performance issues.
You can customize the appearance of the custom node by using the various style props that are available, such as color, borderColor, and borderWidth.
Adding a Sub Flow
In addition to creating a flow with multiple nodes and edges, you can also create a sub flow by using the Flow component from the Reactflow library. A sub flow is a flow within a flow, and can be used to represent a subprocess or a group of related nodes. To create a sub flow, you will need to wrap your nodes and edges in a Flow component and provide a label prop to display a label on the sub flow.
For example, you could create a sub flow like this:
{
id: "parent_group1",
type: "group",
data: {
label: "parent 1",
},
position: {
x: 250,
y: 250,
},
style: {
width: 200,
height: 200,
},
},
{
id: "child1_group1",
type: "input",
data: { label: "child node 1" },
position: { x: 10, y: 10 },
parentNode: "parent_group1",
extent: "parent",
},
{
id: "child2_group1",
data: { label: "child node 2" },
position: { x: 10, y: 90 },
parentNode: "parent_group1",
extent: "parent",
}

Notice the 'type' of first node and 'parentNode' of the other two.
Example Code
index.tsx
import Head from "next/head";
import Image from "next/image";
import { Inter } from "@next/font/google";
import styles from "../styles/Home.module.css";
import { useCallback, useMemo, useState } from "react";
import ReactFlow, {
MiniMap,
Controls,
Background,
useNodesState,
useEdgesState,
addEdge,
applyEdgeChanges,
applyNodeChanges,
NodeChange,
EdgeChange,
Connection,
Edge,
MarkerType,
} from "reactflow";
// 👇 you need to import the reactflow styles
import "reactflow/dist/style.css";
import { Button } from "primereact/button";
import { Dialog } from "primereact/dialog";
import { InputText } from "primereact/inputtext";
import { InputNumber } from "primereact/inputnumber";
import CustomNodeTest from "./myCustomNode";
const inter = Inter({ subsets: ["latin"] });
export default function Home() {
const initialNodes = [
{
id: "1",
type: "input",
position: { x: 100, y: 100 },
data: { label: "Styled First Node" },
style: {
color: "white",
background: "purple",
padding: "2px",
borderRadius: "4px",
width: "75px",
fontSize: "10px",
},
},
{
id: "2",
position: { x: 100, y: 500 },
data: { label: "Second" },
type: "output",
},
{
id: "3",
position: { x: 350, y: 500 },
data: { label: "Third" },
type: "output",
},
{
id: "parent_group1",
type: "group",
data: {
label: "parent 1",
},
position: {
x: 250,
y: 250,
},
style: {
width: 200,
height: 200,
},
},
{
id: "child1_group1",
type: "input",
data: { label: "child node 1" },
position: { x: 10, y: 10 },
parentNode: "parent_group1",
extent: "parent",
},
{
id: "child2_group1",
data: { label: "child node 2" },
position: { x: 10, y: 90 },
parentNode: "parent_group1",
extent: "parent",
},
{
id: "custom-node-1",
type: "customNode",
position: { x: 200, y: 200 },
data: { value: 123 },
},
];
const nodeTypes = useMemo(() => ({ customNode: CustomNodeTest }), []);
const initialEdges = [
{
id: "e1-2",
source: "1",
target: "2",
label: "Edge with fill, color, arrowType",
labelStyle: { fill: "red", fontWeight: 700 },
labelBgPadding: [8, 4],
labelBgBorderRadius: 4,
labelBgStyle: { fill: "#FFCC00", color: "#fff", fillOpacity: 0.7 },
markerEnd: {
type: MarkerType.ArrowClosed,
},
},
];
const [displayDialog, setDisplayDialog] = useState<boolean>(false);
const [nodeName, setNodeName] = useState<string>("");
const [xPosition, setXPosition] = useState<number | null>(null);
const [yPosition, setYPosition] = useState<number | null>(null);
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const onNodesChange = useCallback(
(changes: NodeChange[]) =>
setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes]
);
const onEdgesChange = useCallback(
(changes: EdgeChange[]) =>
setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges]
);
const onConnect = useCallback(
(connection: Edge<any> | Connection) =>
setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
const renderFooter = () => {
return (
<div>
<Button
label="No"
icon="pi pi-times"
onClick={() => setDisplayDialog(false)}
className="p-button-text"
/>
<Button
label="Yes"
icon="pi pi-check"
onClick={() => {
handleSetupNode();
}}
autoFocus
/>
</div>
);
};
const handleAddNode = () => {
setDisplayDialog(true);
};
const handleSetupNode = () => {
if (xPosition && yPosition && nodeName) {
setNodes([
...nodes,
{
id: `${nodes.length + 1}`,
position: { x: xPosition ?? 0, y: yPosition ?? 0 },
data: { label: nodeName },
type: "default",
},
]);
setDisplayDialog(false);
} else return;
};
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="absolute top-0 right-0 z-[9999]">
<Button
label="Add Node"
className="cursor-pointer"
icon="pi pi-plus"
onClick={handleAddNode}
/>
</div>
<Dialog
header="Add Node"
visible={displayDialog}
style={{ width: "50vw" }}
footer={renderFooter()}
onHide={() => setDisplayDialog(false)}
>
<div className="flex flex-col gap-4">
<div className="pt-6">
<span className="p-float-label">
<InputText
id="nameOfNode"
value={nodeName}
onChange={(e) => setNodeName(e.target.value)}
/>
<label htmlFor="nameOfNode">Node Name: </label>
</span>
</div>
<div className="flex gap-4 pt-6">
<span className="p-float-label">
<InputNumber
id="xPos"
value={xPosition}
onChange={(e) => {
setXPosition(e.value!);
}}
/>
<label htmlFor="xPos">X Position: </label>
</span>
<span className="p-float-label">
<InputNumber
id="yPos"
value={yPosition}
onChange={(e) => setYPosition(e.value!)}
/>
<label htmlFor="yPos">Y Position: </label>
</span>
</div>
</div>
</Dialog>
<div style={{ height: "100vh", width: "100vw" }} className="relative">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
attributionPosition="top-right"
>
<MiniMap />
<Controls />
<Background />
</ReactFlow>
</div>
</>
);
}
myCustomNode.tsx
import { useCallback } from "react";
const handleStyle = { left: 10 };
function CustomNodeTest({ data }: any) {
const onChange = useCallback((evt: any) => {
console.log(evt.target.value);
}, []);
return (
<div className="p-2 rounded border border-slate-800">
<label htmlFor="text">Custom Node with text input: </label>
<input id="text" name="text" onChange={onChange} />
</div>
);
}
export default CustomNodeTest;
Codesandbox: https://codesandbox.io/s/example-react-flow-twwxhb
References
Conclusion
Reactflow is a powerful library that makes it easy to create interactive, node-based user interfaces. Whether you want to build a visual workflow application or a data visualization tool, Reactflow provides the tools you need to get started. With its simple API and customizable appearance, Reactflow is a great choice for any developer looking to build a node-based user interface.