Custom Nodes
A powerful feature of React Flow is the ability to create custom nodes. This gives you the flexibility to render anything you want within your nodes. We generally recommend creating your own custom nodes rather than relying on built-in ones. With custom nodes, you can add as many source and target handles as you like—or even embed form inputs, charts, and other interactive elements.
In this section, we’ll walk through creating a custom node featuring an input field that updates text elsewhere in your application. For further examples, we recommend checking out our Custom Node Example.
Implementing a Custom Node
To create a custom node, all you need to do is create a React component. React Flow will automatically wrap it in an interactive container that injects essential props like the node’s id, position, and data, and provides functionality for selection, dragging, and connecting handles. For a full reference on all available custom node props, see the Custom Node Props.
Let’s dive into an example by creating a custom node called TextUpdaterNode
. For this, we’ve added a simple input field with a change handler. React Flow has a few handy pre-built components to simplify the process of creating custom nodes. We will use the Handle
component to allow our custom node to connect with other nodes.
import { useCallback } from 'react';
import { Handle, Position } from '@xyflow/react';
const handleStyle = { left: 10 };
function TextUpdaterNode({ data }) {
const onChange = useCallback((evt) => {
console.log(evt.target.value);
}, []);
return (
<>
<Handle type="target" position={Position.Top} />
<div>
<label htmlFor="text">Text:</label>
<input id="text" name="text" onChange={onChange} className="nodrag" />
</div>
<Handle type="source" position={Position.Bottom} id="a" />
<Handle type="source" position={Position.Bottom} id="b" style={handleStyle} />
</>
);
}
Adding the Node Type
You can add a new node type to React Flow by adding it to the nodeTypes
prop like below. We define the nodeTypes
outside of the component to prevent re-renderings.
const nodeTypes = {
textUpdater: TextUpdaterNode
};
function Flow() {
...
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
style={rfStyle}
/>
);
}
If nodeTypes
are defined inside a component, they must be memoized. Otherwise, React creates a new object on every render, which leads to performance issues and bugs. Here’s how you can memoize the nodeTypes
object inside a component using the useMemo
hook:
const nodeTypes = useMemo(() => ({ textUpdater: TextUpdaterNode }), []);
return <ReactFlow nodeTypes={nodeTypes} />;
After defining your new node type, you can use it by using the type
node option:
const nodes = [
{
id: 'node-1',
type: 'textUpdater',
position: { x: 0, y: 0 },
data: { value: 123 },
},
];
After putting all together and adding some basic styles we get a custom node that prints text to the console:
import { useCallback, useState } from 'react';
import {
ReactFlow,
addEdge,
applyEdgeChanges,
applyNodeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import TextUpdaterNode from './TextUpdaterNode';
const rfStyle = {
backgroundColor: '#B8CEFF',
};
const initialNodes = [
{
id: 'node-1',
type: 'textUpdater',
position: { x: 0, y: 0 },
data: { value: 123 },
},
];
// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component
const nodeTypes = { textUpdater: TextUpdaterNode };
function Flow() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState([]);
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
style={rfStyle}
/>
);
}
export default Flow;
Utility Classes
React Flow provides several built-in utility CSS classes to help you fine-tune how interactions work within your custom nodes.
nodrag
In the example above, we added the class nodrag
to the input. This ensures that interacting with the input field doesn’t trigger a drag, allowing you to select the text within the field.
Nodes have a drag
class name in place by default. However, this class name can affect the behaviour of the event listeners inside your custom nodes. To prevent unexpected behaviours, add a nodrag
class name to elements with an event listener. This prevents the default drag behavior as well as the default node selection behavior when elements with this class are clicked.
export default function CustomNode(props: NodeProps) {
return (
<div>
<input className="nodrag" type="range" min={0} max={100} />
</div>
);
}
nowheel
If your custom node contains scrollable content, you can apply the nowheel
class. This disables the canvas’ default pan behavior when you scroll inside your custom node, ensuring that only the content scrolls instead of moving the entire canvas.
export default function CustomNode(props: NodeProps) {
return (
<div className="nowheel" style={{ overflow: 'auto' }}>
<p>Scrollable content...</p>
</div>
);
}
Applying these utility classes helps you control interaction on a granular level. You can custimize these class names inside React Flow’s style props.
When creating your own custom nodes, you will also need to remember to style them! Unlike the built-in nodes, custom nodes have no default styles, so feel free to use any styling method you prefer, such as styled components or Tailwind CSS.
Using Multiple Handles
When defining edge connections to other nodes using these handles, simply using the node id
isn’t enough. You will also need to specify a handle id
. In this case, we assign an id a
to one source handle and an id b
to the other source handle.
In React Flow, edges can be connected to specific handles within a node using the properties sourceHandle
(for the edge’s starting point) and targetHandle
(for the edge’s ending point). When you have multiple handles on a node—for example, two source handles labeled “a
” and “b
”, simply specifying the node’s id
isn’t enough for React Flow to know which connection point to use. By defining sourceHandle
or targetHandle
with the appropriate handle id
, you instruct React Flow to attach the edge to that specific handle, ensuring that connections are made where you intend.
const initialEdges = [
{ id: 'edge-1', source: 'node-1', sourceHandle: 'a', target: 'node-2' },
{ id: 'edge-2', source: 'node-1', sourceHandle: 'b', target: 'node-3' },
];
In this case, the source node is node-1
for both handles but the handle id
s are different. One comes from handle id "a"
and the other one from "b"
. Both edges also have different target nodes:
import { useCallback, useState } from 'react';
import {
ReactFlow,
addEdge,
applyEdgeChanges,
applyNodeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import TextUpdaterNode from './TextUpdaterNode';
const rfStyle = {
backgroundColor: '#B8CEFF',
};
const initialNodes = [
{
id: 'node-1',
type: 'textUpdater',
position: { x: 0, y: 0 },
data: { value: 123 },
},
{
id: 'node-2',
type: 'output',
targetPosition: 'top',
position: { x: 0, y: 200 },
data: { label: 'node 2' },
},
{
id: 'node-3',
type: 'output',
targetPosition: 'top',
position: { x: 200, y: 200 },
data: { label: 'node 3' },
},
];
const initialEdges = [
{ id: 'edge-1', source: 'node-1', target: 'node-2', sourceHandle: 'a' },
{ id: 'edge-2', source: 'node-1', target: 'node-3', sourceHandle: 'b' },
];
// we define the nodeTypes outside of the component to prevent re-renderings
// you could also use useMemo inside the component
const nodeTypes = { textUpdater: TextUpdaterNode };
function Flow() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
);
const onConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
fitView
style={rfStyle}
/>
);
}
export default Flow;
If you are programmatically changing the position or number of handles
in your custom node, you will need to use the
useUpdateNodeInternals
hook
to properly notify React Flow of changes.