Adding Interactivity
Now that we’ve built our first flow, let’s add *interactivity so you can select, drag, and remove nodes and edges.
In the previous guide we created a flow with
two nodes. Those nodes were passed as a static array to the nodes prop, and
thus the flow was not interactive by default. Passing nodes and edges to the
ReactFlow component is called controlled flow. This means that you are in
control of the state of the nodes and edges, and thus you need to pass event
handlers to the ReactFlow component, and make the nodes and edges arrays
reactive to handle changes to the nodes and edges. You can read more
about this in the State Management guide.
Handling change events
By default React Flow doesn’t manage any internal state updates besides handling the
viewport. As you would with an HTML <input /> element you need to pass
event handlers to React Flow in order to apply
triggered changes to your nodes and edges.
To manage changes, we’ll be using useState with two helper functions from React Flow:
applyNodeChanges and
applyEdgeChanges. So let’s import these
functions:
import { useState, useCallback } from 'react';
import { ReactFlow, applyEdgeChanges, applyNodeChanges } from '@xyflow/react';Define nodes and edges
We need to define initial nodes and edges. These will be the starting point for our flow.
const initialNodes = [
{
id: 'n1',
position: { x: 0, y: 0 },
data: { label: 'Node 1' },
type: 'input',
},
{
id: 'n2',
position: { x: 100, y: 100 },
data: { label: 'Node 2' },
},
];
const initialEdges = [
{
id: 'n1-n2',
source: 'n1',
target: 'n2',
},
];Initialize state
In our component, we’ll call the useState hook to manage the state of our nodes and
edges:
export default function App() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
return (
<div style={{ height: '100%', width: '100%' }}>
<ReactFlow>
<Background />
<Controls />
</ReactFlow>
</div>
);
}Add event handlers
We need to create two event handlers:
onNodesChange and
onEdgesChange. They will be used to update
the state of our nodes and edges when changes occur, such as dragging or deleting an
element. Go ahead and add these handlers to your component:
const onNodesChange = useCallback(
(changes) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)),
[],
);
const onEdgesChange = useCallback(
(changes) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)),
[],
);Pass them to ReactFlow
Now we can pass our nodes, edges, and event handlers to the <ReactFlow /> component:
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
>
<Background />
<Controls />
</ReactFlow>Interactive flow
And that’s it! You now have a basic interactive flow 🎉
When you drag or select a node, the onNodesChange handler is triggered. The
applyNodeChanges function then uses these change events to update the current state of
your nodes. Here’s how it all comes together. Try clicking and dragging a node to move it
around and watch the UI update in real time.
Example: learn/make-it-interactive-1
App.jsx
import { useState, useCallback } from 'react';
import {
ReactFlow,
Controls,
Background,
applyNodeChanges,
applyEdgeChanges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes = [
{
id: 'n1',
data: { label: 'Node 1' },
position: { x: 0, y: 0 },
type: 'input',
},
{
id: 'n2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
},
];
const initialEdges = [
{ id: 'n1-n2', source: 'n1', target: 'n2', label: 'connects with', type: 'step' },
];
function Flow() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const onNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[],
);
const onEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[],
);
return (
<div style={{ height: '100%' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
colorMode="system"
>
<Background />
<Controls />
</ReactFlow>
</div>
);
}
export default Flow;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>
<style>
html,
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
</style>
</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';
const container = document.querySelector('#app');
const root = createRoot(container);
root.render(<App />);Handling connections
One last piece is missing: connecting nodes interactively. For this, we need to implement
an onConnect handler.