Drag and Drop
In these examples, we use a custom Sidebar component to create new nodes when dragging from the sidebar into the React Flow pane.
A drag and drop user interface is very common for node-based workflow editors. The drag and drop behavior outside of the React Flow pane is not built in but can be implemented with the native HTML Drag and Drop API , Pointer Events , or a third party library like react-draggable .
Drag and Drop with HTML Drag and Drop API
The HTML Drag and Drop API is a native API that is supported by all major browsers. It is a simple API that allows you to drag and drop elements between different elements on the page.
Note: HTML Drag and Drop API is not properly supported on touch devices. See the example below for an alternative approach using pointer events.
import React, { useRef, useCallback } from 'react';
import {
ReactFlow,
ReactFlowProvider,
addEdge,
useNodesState,
useEdgesState,
Controls,
useReactFlow,
Background,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import Sidebar from './Sidebar';
import { DnDProvider, useDnD } from './DnDContext';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
let id = 0;
const getId = () => `dndnode_${id++}`;
const DnDFlow = () => {
const reactFlowWrapper = useRef(null);
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const { screenToFlowPosition } = useReactFlow();
const [type] = useDnD();
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
const onDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const onDrop = useCallback(
(event) => {
event.preventDefault();
// check if the dropped element is valid
if (!type) {
return;
}
// project was renamed to screenToFlowPosition
// and you don't need to subtract the reactFlowBounds.left/top anymore
// details: https://reactflow.dev/whats-new/2023-11-10
const position = screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const newNode = {
id: getId(),
type,
position,
data: { label: `${type} node` },
};
setNodes((nds) => nds.concat(newNode));
},
[screenToFlowPosition, type],
);
const onDragStart = (event, nodeType) => {
setType(nodeType);
event.dataTransfer.setData('text/plain', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className="dndflow">
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onDrop={onDrop}
onDragStart={onDragStart}
onDragOver={onDragOver}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
};
export default () => (
<ReactFlowProvider>
<DnDProvider>
<DnDFlow />
</DnDProvider>
</ReactFlowProvider>
);
Drag and Drop with Pointer Events
Another way to implement drag and drop is to use pointer events.
The Pointer Events API is a modern API that is supported by all major browsers. It is slightly more complex to implement than the HTML Drag and Drop API, but by using pointer events we can make sure that the drag and drop behavior is consistent across all devices, both mouse and touch.
import { useCallback } from 'react';
import {
Background,
Connection,
Controls,
ReactFlow,
ReactFlowProvider,
addEdge,
useEdgesState,
useNodesState,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Sidebar } from './Sidebar';
import { DnDProvider } from './useDnD';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
function DnDFlow() {
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<div className="dndflow">
<div className="reactflow-wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
}
export default () => (
<ReactFlowProvider>
<DnDProvider>
<DnDFlow />
</DnDProvider>
</ReactFlowProvider>
);
Drag and Drop with Neodrag
Neodrag is a library that provides a simple and easy to use drag and drop API. In a real-world application, you may want to use the Neodrag library to handle the drag and drop behavior in a cross-platform compatible way, compatible with both mouse and touch devices.
import { useCallback } from 'react';
import {
Background,
Connection,
Controls,
ReactFlow,
ReactFlowProvider,
addEdge,
useEdgesState,
useNodesState,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Sidebar } from './Sidebar';
const initialNodes = [
{
id: '1',
type: 'input',
data: { label: 'input node' },
position: { x: 250, y: 5 },
},
];
function DnDFlow() {
const [nodes, _, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onConnect = useCallback(
(params: Connection) => setEdges((eds) => addEdge(params, eds)),
[],
);
return (
<div className="dndflow">
<div className="reactflow-wrapper">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
>
<Controls />
<Background />
</ReactFlow>
</div>
<Sidebar />
</div>
);
}
export default () => (
<ReactFlowProvider>
<DnDFlow />
</ReactFlowProvider>
);