Create the component
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.
export function TextUpdaterNode(props) {
const onChange = useCallback((evt) => {
console.log(evt.target.value);
}, []);
return (
<div className="text-updater-node">
<div>
<label htmlFor="text">Text:</label>
<input id="text" name="text" onChange={onChange} className="nodrag" />
</div>
</div>
);
}Initialize nodeTypes
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,
};Pass nodeTypes to React Flow
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
/>Update node definitions
After defining your new node type, you can use it by specifying the type property on
your node definition:
const nodes = [
{
id: 'node-1',
type: 'textUpdater',
position: { x: 0, y: 0 },
data: { value: 123 },
},
];Flow with custom node
After putting all together and adding some basic styles we get a custom node that prints text to the console:
Example: learn/custom-node
App.jsx
import { useState } from 'react';
import { ReactFlow } 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([]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
fitView
style={rfStyle}
colorMode="system"
/>
);
}
export default Flow;TextUpdaterNode.jsx
import { useCallback } from 'react';
function TextUpdaterNode(props) {
const onChange = useCallback((evt) => {
console.log(evt.target.value);
}, []);
return (
<div className="text-updater-node">
<div>
<label htmlFor="text">Text:</label>
<input id="text" name="text" onChange={onChange} className="nodrag" />
</div>
</div>
);
}
export default TextUpdaterNode;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.text-updater-node {
height: 50px;
border: 1px solid #eee;
padding: 5px;
border-radius: 5px;
background: white;
}
.text-updater-node label {
display: block;
color: #777;
font-size: 12px;
}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 />);Full code example 🏁
Example: learn/custom-node
App.jsx
import { useState } from 'react';
import { ReactFlow } 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([]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
fitView
style={rfStyle}
colorMode="system"
/>
);
}
export default Flow;TextUpdaterNode.jsx
import { useCallback } from 'react';
function TextUpdaterNode(props) {
const onChange = useCallback((evt) => {
console.log(evt.target.value);
}, []);
return (
<div className="text-updater-node">
<div>
<label htmlFor="text">Text:</label>
<input id="text" name="text" onChange={onChange} className="nodrag" />
</div>
</div>
);
}
export default TextUpdaterNode;index.css
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
.text-updater-node {
height: 50px;
border: 1px solid #eee;
padding: 5px;
border-radius: 5px;
background: white;
}
.text-updater-node label {
display: block;
color: #777;
font-size: 12px;
}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 />);To enable your custom node to connect with other nodes, check out the Handles page to learn how to add source and target handles.