Computing Flows
Usually with React Flow, developers handle their data outside of React Flow by sending it somewhere else, like on a server or a database. Instead, in this guide we’ll show you how to compute data flows directly inside of React Flow. You can use this for updating a node based on connected data, or for building an app that runs entirely inside the browser.
What are we going to build?
By the end of this guide, you will build an interactive flow graph that generates a color out of three separate number input fields (red, green and blue), and determines whether white or black text would be more readable on that background color.
Example: learn/computing-6
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
import Lightness from './Lightness';
import Log from './Log';
const nodeTypes = {
NumberInput,
ColorPreview,
Lightness,
Log,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: {
label: 'Color',
value: { r: undefined, g: undefined, b: undefined },
},
},
{
type: 'Lightness',
id: 'lightness',
position: { x: 350, y: 75 },
},
{
id: 'log-1',
type: 'Log',
position: { x: 500, y: 0 },
data: { label: 'Use black font', fontColor: 'black' },
},
{
id: 'log-2',
type: 'Log',
position: { x: 500, y: 140 },
data: { label: 'Use white font', fontColor: 'white' },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
{
id: 'color-lightness',
source: 'color',
target: 'lightness',
},
{
id: 'lightness-log-1',
source: 'lightness',
sourceHandle: 'light',
target: 'log-1',
},
{
id: 'lightness-log-2',
source: 'lightness',
sourceHandle: 'dark',
target: 'log-2',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function CustomHandle({ id, label, onChange }) {
const connections = useNodeConnections({
handleType: 'target',
handleId: id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}
function ColorPreview({ id, data }) {
const { updateNodeData } = useReactFlow();
return (
<div
className="node"
style={{
background: data.value
? `rgb(${data.value.r}, ${data.value.g}, ${data.value.b})`
: 'rgb(0, 0, 0)',
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, r: value } };
});
}}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, g: value } };
});
}}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, b: value } };
});
}}
/>
<Handle type="source" position={Position.Right} id="output" />
</div>
);
}
export default ColorPreview;Lightness.jsx
import { useState, useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function LightnessNode({ id }) {
const { updateNodeData } = useReactFlow();
const connections = useNodeConnections({ handleType: 'target' });
const nodesData = useNodesData(connections?.[0].source);
const [lightness, setLightness] = useState('dark');
useEffect(() => {
if (nodesData.data?.value) {
const color = nodesData.data.value;
const isLight =
0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b >= 128;
setLightness(isLight ? 'light' : 'dark');
const newNodeData = isLight
? { light: color, dark: null }
: { light: null, dark: color };
updateNodeData(id, newNodeData);
} else {
setLightness('dark');
updateNodeData(id, { light: null, dark: { r: 0, g: 0, b: 0 } });
}
}, [nodesData, updateNodeData]);
return (
<div
className="lightness-node"
style={{
background: lightness === 'light' ? 'white' : 'black',
color: lightness === 'light' ? 'black' : 'white',
}}
>
<Handle type="target" position={Position.Left} />
<p style={{ marginRight: 10 }}>Light</p>
<Handle
type="source"
id="light"
position={Position.Right}
style={{ top: 25 }}
/>
<p style={{ marginRight: 10 }}>Dark</p>
<Handle
type="source"
id="dark"
position={Position.Right}
style={{ top: 75 }}
/>
</div>
);
}
export default LightnessNode;Log.jsx
import { Handle, useNodeConnections, useNodesData } from '@xyflow/react';
function Log({ data }) {
const connections = useNodeConnections({ handleType: 'target' });
const nodeData = useNodesData(connections?.[0].source);
const color = nodeData.data
? nodeData.data[connections?.[0].sourceHandle]
: null;
return (
<div
className="log-node"
style={{
background: color ? `rgb(${color.r}, ${color.g}, ${color.b})` : 'white',
color: color ? data.fontColor : 'black',
}}
>
{color ? data.label : 'Do nothing'}
<Handle type="target" position="left" />
</div>
);
}
export default Log;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}
.lightness-node {
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: end;
justify-content: center;
text-align: center;
border-radius: 10px;
}
.log-node {
width: 80px;
height: 80px;
word-wrap: break-word;
padding: 5px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Creating custom nodes
Let’s start by creating a custom input node (NumberInput.js) and add three instances of it. We will be using a controlled <input type="number" /> and limit it to integer numbers between 0 - 255 inside the onChange event handler.
import { useCallback, useState } from 'react';
import { Handle, Position } from '@xyflow/react';
function NumberInput({ id, data }) {
const [number, setNumber] = useState(0);
const onChange = useCallback((evt) => {
const cappedNumber = Math.round(
Math.min(255, Math.max(0, evt.target.value)),
);
setNumber(cappedNumber);
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;Next, we’ll add a new custom node (ColorPreview.js) with one target handle for each color channel and a background that displays the resulting color. We can use mix-blend-mode: 'difference'; to make the text color always readable.
Example: learn/computing
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
const nodeTypes = {
NumberInput,
ColorPreview,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red' },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green' },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue' },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: { label: 'Color' },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { Handle, Position } from '@xyflow/react';
function ColorPreview() {
const color = { r: 0, g: 0, b: 0 };
return (
<div
className="node"
style={{
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
}}
>
<div>
<Handle
type="target"
position={Position.Left}
id="red"
className="handle"
/>
<label htmlFor="red" className="label">
R
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="green"
className="handle"
/>
<label htmlFor="green" className="label">
G
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="blue"
className="handle"
/>
<label htmlFor="red" className="label">
B
</label>
</div>
</div>
);
}
export default ColorPreview;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position } from '@xyflow/react';
function NumberInput({ id, data }) {
const [number, setNumber] = useState(0);
const onChange = useCallback((evt) => {
const cappedNumber = Math.round(
Math.min(255, Math.max(0, evt.target.value)),
);
setNumber(cappedNumber);
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Computing data
How do we get the data from the input nodes to the color node? This is a two step process that involves two hooks created for this exact purpose:
- Store each number input value inside the node’s
dataobject with help of theupdateNodeDatacallback. - Find out which nodes are connected by using
useNodeConnectionsand then useuseNodesDatafor receiving the data from the connected nodes.
Step 1: Writing values to the data object
First let’s add some initial values for the input nodes inside the data object in our initialNodes array and use them as an initial state for the input nodes.
Then we’ll grab the function updateNodeData from the useReactFlow hook and use it to update the data object of the node with a new value whenever the input changes.
Example: learn/computing-2
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
const nodeTypes = {
NumberInput,
ColorPreview,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: { label: 'Color' },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { Handle, Position } from '@xyflow/react';
function ColorPreview() {
const color = { r: 0, g: 0, b: 0 };
return (
<div
className="node"
style={{
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
}}
>
<div>
<Handle
type="target"
position={Position.Left}
id="red"
className="handle"
/>
<label htmlFor="red" className="label">
R
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="green"
className="handle"
/>
<label htmlFor="green" className="label">
G
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="blue"
className="handle"
/>
<label htmlFor="red" className="label">
B
</label>
</div>
</div>
);
}
export default ColorPreview;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Step 2: Getting data from connected nodes
We start by determining all connections for each node with the useNodeConnections hook and then fetching the data for the first connected node with updateNodeData.
And there you go! Try changing the input values and see the color change in real time.
Example: learn/computing-3
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
const nodeTypes = {
NumberInput,
ColorPreview,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: { label: 'Color' },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import {
Handle,
Position,
useNodesData,
useNodeConnections,
} from '@xyflow/react';
function ColorPreview() {
const redConnections = useNodeConnections({
handleType: 'target',
handleId: 'red',
});
const redNodeData = useNodesData(redConnections?.[0].source);
const greenConnections = useNodeConnections({
handleType: 'target',
handleId: 'green',
});
const greenNodeData = useNodesData(greenConnections?.[0].source);
const blueConnections = useNodeConnections({
handleType: 'target',
handleId: 'blue',
});
const blueNodeData = useNodesData(blueConnections?.[0].source);
const color = {
r: redNodeData?.data ? redNodeData.data.value : 0,
g: greenNodeData?.data ? greenNodeData.data.value : 0,
b: blueNodeData?.data ? blueNodeData.data.value : 0,
};
return (
<div
className="node"
style={{
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
}}
>
<div>
<Handle
type="target"
position={Position.Left}
id="red"
className="handle"
/>
<label htmlFor="red" className="label">
R
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="green"
className="handle"
/>
<label htmlFor="green" className="label">
G
</label>
</div>
<div>
<Handle
type="target"
position={Position.Left}
id="blue"
className="handle"
/>
<label htmlFor="red" className="label">
B
</label>
</div>
</div>
);
}
export default ColorPreview;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Improving the code
It might seem awkward to get the connections first, and then the data separately for each handle. For nodes with multiple handles like these, you should consider creating a custom handle component that isolates connection states and node data binding. We can create one inline.
// {...}
function CustomHandle({ id, label, onChange }) {
const connections = useNodeConnections({
handleType: 'target',
handleId: id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}We can promote color to local state and declare each handle like this:
// {...}
function ColorPreview() {
const [color, setColor] = useState({ r: 0, g: 0, b: 0 });
return (
<div
className="node"
style={{
background: `rgb(${color.r}, ${color.g}, ${color.b})`,
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => setColor((c) => ({ ...c, r: value }))}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => setColor((c) => ({ ...c, g: value }))}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => setColor((c) => ({ ...c, b: value }))}
/>
</div>
);
}
export default ColorPreview;Getting more complex
Now we have a simple example of how to pipe data through React Flow. What if we want to do something more complex, like transforming the data along the way? Or even take different paths? We can do that too!
Continuing the flow
Let’s extend our flow. Start by adding an output <Handle type="source" position={Position.Right} /> to the color node and remove the local component state.
Next, we add a new node (Lightness.js) that takes in a color object and determines if it is either a light or dark color. We can use the relative luminance formula
luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
to calculate the perceived brightness of a color (0 being the darkest and 255 being the brightest). We can assume everything >= 128 is a light color.
Example: learn/computing-4
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
import Lightness from './Lightness';
const nodeTypes = {
NumberInput,
ColorPreview,
Lightness,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: {
label: 'Color',
value: { r: undefined, g: undefined, b: undefined },
},
},
{
type: 'Lightness',
id: 'lightness',
position: { x: 350, y: 75 },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
{
id: 'color-lightness',
source: 'color',
target: 'lightness',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function CustomHandle({ id, label, onChange }) {
const connections = useNodeConnections({
handleType: 'target',
handleId: id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}
function ColorPreview({ id, data }) {
const { updateNodeData } = useReactFlow();
return (
<div
className="node"
style={{
background: data.value
? `rgb(${data.value.r}, ${data.value.g}, ${data.value.b})`
: 'rgb(0, 0, 0)',
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, r: value } };
});
}}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, g: value } };
});
}}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, b: value } };
});
}}
/>
<Handle type="source" position={Position.Right} id="output" />
</div>
);
}
export default ColorPreview;Lightness.jsx
import { useState, useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
} from '@xyflow/react';
function LightnessNode() {
const connections = useNodeConnections({ handleType: 'target' });
const nodesData = useNodesData(connections?.[0].source);
const [lightness, setLightness] = useState('dark');
useEffect(() => {
if (nodesData?.data) {
const color = nodesData.data.value;
setLightness(
0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b >= 128
? 'light'
: 'dark',
);
} else {
setLightness('dark');
}
}, [nodesData]);
return (
<div
className="lightness-node"
style={{
background: lightness === 'light' ? 'white' : 'black',
color: lightness === 'light' ? 'black' : 'white',
}}
>
<Handle type="target" position={Position.Left} />
<div>
This color is
<p style={{ fontWeight: 'bold', fontSize: '1.2em' }}>{lightness}</p>
</div>
</div>
);
}
export default LightnessNode;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}
.lightness-node {
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 10px;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Conditional branching
What if we would like to take a different path in our flow based on the perceived lightness? Let’s give our lightness node two source handles light and dark and separate the node data object by source handle IDs. This is needed if you have multiple source handles to distinguish between each source handle’s data.
But what does it mean to “take a different route”? One solution would be to assume that null or undefined data hooked up to a target handle is considered a “stop”. In our case we can write the incoming color into data.values.light if it’s a light color and into data.values.dark if it’s a dark color and set the respective other value to null.
Don’t forget to add flex-direction: column; and align-items: end; to reposition the handle labels.
Example: learn/computing-5
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
import Lightness from './Lightness';
const nodeTypes = {
NumberInput,
ColorPreview,
Lightness,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: {
label: 'Color',
value: { r: undefined, g: undefined, b: undefined },
},
},
{
type: 'Lightness',
id: 'lightness',
position: { x: 350, y: 75 },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
{
id: 'color-lightness',
source: 'color',
target: 'lightness',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function CustomHandle({ id, label, onChange }) {
const connections = useNodeConnections({
type: 'target',
handleId: id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}
function ColorPreview({ id, data }) {
const { updateNodeData } = useReactFlow();
return (
<div
className="node"
style={{
background: data.value
? `rgb(${data.value.r}, ${data.value.g}, ${data.value.b})`
: 'rgb(0, 0, 0)',
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, r: value } };
});
}}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, g: value } };
});
}}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, b: value } };
});
}}
/>
<Handle type="source" position={Position.Right} id="output" />
</div>
);
}
export default ColorPreview;Lightness.jsx
import { useState, useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function LightnessNode({ id }) {
const { updateNodeData } = useReactFlow();
const connections = useNodeConnections({ handleType: 'target' });
const nodesData = useNodesData(connections?.[0].source);
const [lightness, setLightness] = useState('dark');
useEffect(() => {
if (nodesData?.data) {
const color = nodesData.data.value;
const isLight =
0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b >= 128;
setLightness(isLight ? 'light' : 'dark');
const newNodeData = isLight
? { light: color, dark: null }
: { light: null, dark: color };
updateNodeData(id, newNodeData);
} else {
setLightness('dark');
updateNodeData(id, { light: null, dark: { r: 0, g: 0, b: 0 } });
}
}, [nodesData, updateNodeData]);
return (
<div
className="lightness-node"
style={{
background: lightness === 'light' ? 'white' : 'black',
color: lightness === 'light' ? 'black' : 'white',
}}
>
<Handle type="target" position={Position.Left} />
<p style={{ marginRight: 10 }}>Light</p>
<Handle
type="source"
id="light"
position={Position.Right}
style={{ top: 25 }}
/>
<p style={{ marginRight: 10 }}>Dark</p>
<Handle
type="source"
id="dark"
position={Position.Right}
style={{ top: 75 }}
/>
</div>
);
}
export default LightnessNode;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}
.lightness-node {
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: end;
justify-content: center;
text-align: center;
border-radius: 10px;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Cool! Now we only need a last node to see if it actually works… We can create a custom debugging node (Log.js) that displays the hooked up data, and we’re done!
Example: learn/computing-6
App.jsx
import { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import NumberInput from './NumberInput';
import ColorPreview from './ColorPreview';
import Lightness from './Lightness';
import Log from './Log';
const nodeTypes = {
NumberInput,
ColorPreview,
Lightness,
Log,
};
const initialNodes = [
{
type: 'NumberInput',
id: '1',
data: { label: 'Red', value: 255 },
position: { x: 0, y: 0 },
},
{
type: 'NumberInput',
id: '2',
data: { label: 'Green', value: 0 },
position: { x: 0, y: 100 },
},
{
type: 'NumberInput',
id: '3',
data: { label: 'Blue', value: 115 },
position: { x: 0, y: 200 },
},
{
type: 'ColorPreview',
id: 'color',
position: { x: 150, y: 50 },
data: {
label: 'Color',
value: { r: undefined, g: undefined, b: undefined },
},
},
{
type: 'Lightness',
id: 'lightness',
position: { x: 350, y: 75 },
},
{
id: 'log-1',
type: 'Log',
position: { x: 500, y: 0 },
data: { label: 'Use black font', fontColor: 'black' },
},
{
id: 'log-2',
type: 'Log',
position: { x: 500, y: 140 },
data: { label: 'Use white font', fontColor: 'white' },
},
];
const initialEdges = [
{
id: '1-color',
source: '1',
target: 'color',
targetHandle: 'red',
},
{
id: '2-color',
source: '2',
target: 'color',
targetHandle: 'green',
},
{
id: '3-color',
source: '3',
target: 'color',
targetHandle: 'blue',
},
{
id: 'color-lightness',
source: 'color',
target: 'lightness',
},
{
id: 'lightness-log-1',
source: 'lightness',
sourceHandle: 'light',
target: 'log-1',
},
{
id: 'lightness-log-2',
source: 'lightness',
sourceHandle: 'dark',
target: 'log-2',
},
];
function ReactiveFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<ReactFlow
nodeTypes={nodeTypes}
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
colorMode="system"
>
<Background />
</ReactFlow>
);
}
export default ReactiveFlow;ColorPreview.jsx
import { useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function CustomHandle({ id, label, onChange }) {
const connections = useNodeConnections({
handleType: 'target',
handleId: id,
});
const nodeData = useNodesData(connections?.[0].source);
useEffect(() => {
onChange(nodeData?.data ? nodeData.data.value : 0);
}, [nodeData]);
return (
<div>
<Handle
type="target"
position={Position.Left}
id={id}
className="handle"
/>
<label htmlFor="red" className="label">
{label}
</label>
</div>
);
}
function ColorPreview({ id, data }) {
const { updateNodeData } = useReactFlow();
return (
<div
className="node"
style={{
background: data.value
? `rgb(${data.value.r}, ${data.value.g}, ${data.value.b})`
: 'rgb(0, 0, 0)',
}}
>
<CustomHandle
id="red"
label="R"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, r: value } };
});
}}
/>
<CustomHandle
id="green"
label="G"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, g: value } };
});
}}
/>
<CustomHandle
id="blue"
label="B"
onChange={(value) => {
updateNodeData(id, (node) => {
return { value: { ...node.data.value, b: value } };
});
}}
/>
<Handle type="source" position={Position.Right} id="output" />
</div>
);
}
export default ColorPreview;Lightness.jsx
import { useState, useEffect } from 'react';
import {
Handle,
Position,
useNodeConnections,
useNodesData,
useReactFlow,
} from '@xyflow/react';
function LightnessNode({ id }) {
const { updateNodeData } = useReactFlow();
const connections = useNodeConnections({ handleType: 'target' });
const nodesData = useNodesData(connections?.[0].source);
const [lightness, setLightness] = useState('dark');
useEffect(() => {
if (nodesData.data?.value) {
const color = nodesData.data.value;
const isLight =
0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b >= 128;
setLightness(isLight ? 'light' : 'dark');
const newNodeData = isLight
? { light: color, dark: null }
: { light: null, dark: color };
updateNodeData(id, newNodeData);
} else {
setLightness('dark');
updateNodeData(id, { light: null, dark: { r: 0, g: 0, b: 0 } });
}
}, [nodesData, updateNodeData]);
return (
<div
className="lightness-node"
style={{
background: lightness === 'light' ? 'white' : 'black',
color: lightness === 'light' ? 'black' : 'white',
}}
>
<Handle type="target" position={Position.Left} />
<p style={{ marginRight: 10 }}>Light</p>
<Handle
type="source"
id="light"
position={Position.Right}
style={{ top: 25 }}
/>
<p style={{ marginRight: 10 }}>Dark</p>
<Handle
type="source"
id="dark"
position={Position.Right}
style={{ top: 75 }}
/>
</div>
);
}
export default LightnessNode;Log.jsx
import { Handle, useNodeConnections, useNodesData } from '@xyflow/react';
function Log({ data }) {
const connections = useNodeConnections({ handleType: 'target' });
const nodeData = useNodesData(connections?.[0].source);
const color = nodeData.data
? nodeData.data[connections?.[0].sourceHandle]
: null;
return (
<div
className="log-node"
style={{
background: color ? `rgb(${color.r}, ${color.g}, ${color.b})` : 'white',
color: color ? data.fontColor : 'black',
}}
>
{color ? data.label : 'Do nothing'}
<Handle type="target" position="left" />
</div>
);
}
export default Log;NumberInput.jsx
import { useCallback, useState } from 'react';
import { Handle, Position, useReactFlow } from '@xyflow/react';
function NumberInput({ id, data }) {
const { updateNodeData } = useReactFlow();
const [number, setNumber] = useState(data.value);
const onChange = useCallback((evt) => {
const cappedNumber = Math.min(255, Math.max(0, evt.target.value));
setNumber(cappedNumber);
updateNodeData(id, { value: cappedNumber });
}, []);
return (
<div className="number-input">
<div>{data.label}</div>
<input
id={`number-${id}`}
name="number"
type="number"
min="0"
max="255"
onChange={onChange}
className="nodrag"
value={number}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default NumberInput;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.react-flow__node {
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12);
border-radius: 10px;
font-size: 12px;
}
.number-input {
padding: 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 10px;
}
.node {
height: 150px;
width: 150px;
display: flex;
flex-direction: column;
justify-content: space-around;
border-radius: 10px;
}
.handle {
position: relative;
top: 15px;
}
.label {
margin-left: 10px;
mix-blend-mode: difference;
color: white;
font-weight: bold;
}
.lightness-node {
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: end;
justify-content: center;
text-align: center;
border-radius: 10px;
}
.log-node {
width: 80px;
height: 80px;
word-wrap: break-word;
padding: 5px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
}index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Flow Example</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.jsx"></script>
</body>
</html>index.jsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Summary
You have learned how to move data through the flow and transform it along the way. All you need to do is
- store data inside the node’s
dataobject with help ofupdateNodeDatacallback. - find out which nodes are connected by using
useNodeConnectionsand then useuseNodesDatafor receiving the data from the connected nodes.
You can implement branching for example by interpreting incoming data that is undefined as a “stop”. As a side note, most flow graphs that also have a branching usually separate the triggering of nodes from the actual data hooked up to the nodes. Unreal Engines Blueprints are a good example for this.