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.

### Add imports

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.