Delete Middle Node
This example shows you how to recover deleted edges when you remove a node from the
middle of a chain. In other words, if we have three nodes connected in sequence -
a->b->c
- and we deleted the middle node b
, this example shows you how to end
up with the graph a->c
.
To achieve this, we need to make use of a few bits:
- The
onNodesDelete
callback lets us know when a node is deleted. getConnectedEdges
gives us all the edges connected to a node, either as source or target.getIncomers
andgetOutgoers
give us the nodes connected to a node as source or target.
All together, this allows us to take all the nodes connected to the deleted node, and reconnect them to any nodes the deleted node was connected to.
import React, { useCallback } from 'react';
import {
ReactFlow,
Background,
useNodesState,
useEdgesState,
addEdge,
getIncomers,
getOutgoers,
getConnectedEdges,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'Start here...' },
position: { x: -150, y: 0 },
},
{
id: '2',
type: 'input',
data: { label: '...or here!' },
position: { x: 150, y: 0 },
},
{ id: '3', data: { label: 'Delete me.' }, position: { x: 0, y: 100 } },
{ id: '4', data: { label: 'Then me!' }, position: { x: 0, y: 200 } },
{
id: '5',
type: 'output',
data: { label: 'End here!' },
position: { x: 0, y: 300 },
},
];
const initialEdges = [
{ id: '1->3', source: '1', target: '3' },
{ id: '2->3', source: '2', target: '3' },
{ id: '3->4', source: '3', target: '4' },
{ id: '4->5', source: '4', target: '5' },
];
export default function Flow() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(params) => setEdges(addEdge(params, edges)),
[edges],
);
const onNodesDelete = useCallback(
(deleted) => {
setEdges(
deleted.reduce((acc, node) => {
const incomers = getIncomers(node, nodes, edges);
const outgoers = getOutgoers(node, nodes, edges);
const connectedEdges = getConnectedEdges([node], edges);
const remainingEdges = acc.filter(
(edge) => !connectedEdges.includes(edge),
);
const createdEdges = incomers.flatMap(({ id: source }) =>
outgoers.map(({ id: target }) => ({
id: `${source}->${target}`,
source,
target,
})),
);
return [...remainingEdges, ...createdEdges];
}, edges),
);
},
[nodes, edges],
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onNodesDelete={onNodesDelete}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
attributionPosition="top-right"
style={{ backgroundColor: "#F7F9FB" }}
>
<Background />
</ReactFlow>
);
}
Although this example is less than 20 lines of code there’s quite a lot to digest. Let’s break some of it down:
-
Our
onNodesDelete
callback is called with one argument -deleted
- that is an array of every node that was just deleted. If you select an individual node and press the delete key,deleted
will contain just that node, but if you make a selection all the nodes in that selection will be indeleted
. -
We create a new array of edges -
remainingEdges
- that contains all the edges in the flow that have nothing to do with the node(s) we just deleted. -
We create another array of edges by flatMapping over the array of
incomers
. These are nodes that were connected to the deleted node as a source. For each of these nodes, we create a new edge that connects to each node in the array ofoutgoers
. These are nodes that were connected to the deleted node as a target.
For brevity, we’re using object destructuring while at the same time renaming
the variable bound (e.g. ({ id: source }) => ...)
destructures the id
property of the object and binds it to a new variable called source
) but you
don’t need to do this