ExamplesNodes

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:

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 in deleted.

  • 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 of outgoers. 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

Quick Reference