Usage with TypeScript
React Flow is written in TypeScript because we value the additional safety barrier it provides. We export all the types you need for correctly typing data structures and functions you pass to the React Flow component. We also provide a way to extend the types of nodes and edges.
Basic usage
Let's start with the most basic types you need for a simple starting point. Typescript might already infer some of these types, but we will define them explicitly nontheless.
import { useState, useCallback } from 'react';
import {
ReactFlow,
addEdge,
applyNodeChanges,
applyEdgeChanges,
type Node,
type Edge,
type FitViewOptions,
type OnConnect,
type OnNodesChange,
type OnEdgesChange,
type OnNodeDrag,
type NodeTypes,
type DefaultEdgeOptions,
} from '@xyflow/react';
const initialNodes: Node[] = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 5, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 5, y: 100 } },
];
const initialEdges: Edge[] = [{ id: 'e1-2', source: '1', target: '2' }];
const fitViewOptions: FitViewOptions = {
padding: 0.2,
};
const defaultEdgeOptions: DefaultEdgeOptions = {
animated: true,
};
const nodeTypes: NodeTypes = {
num: NumberNode,
txt: TextNode,
};
const onNodeDrag: OnNodeDrag = (_, node) => {
console.log('drag event', node.data);
};
function Flow() {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange: OnNodesChange = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
const onEdgesChange: OnEdgesChange = useCallback(
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges],
);
const onConnect: OnConnect = useCallback(
(connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges],
);
return (
<ReactFlow
nodes={nodes}
nodeTypes={nodeTypes}
edges={edges}
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeDrag={onNodeDrag}
fitView
fitViewOptions={fitViewOptions}
defaultEdgeOptions={defaultEdgeOptions}
/>
);
}
Custom nodes
When working with custom nodes you have the possibility to pass a custom Node
type (or your Node
union) to the NodeProps
type. There are basically two ways to work with custom nodes:
- If you have multiple custom nodes, you want to pass a specific
Node
type as a generic to theNodeProps
type:
import type { Node, NodeProps } from '@xyflow/react';
type NumberNode = Node<{ number: number }, 'number'>;
export default function NumberNode({ data }: NodeProps<NumberNode>) {
return <div>A special number: {data.number}</div>;
}
⚠️ If you specify the node data separately, you need to use type
(an interface
would not work here):
type NumberNodeData = { number: number };
type NumberNode = Node<NumberNodeData, 'number'>;
- If you have one custom node that renders different content based on the node type, you want to pass your
Node
union type as a generic toNodeProps
:
import type { Node, NodeProps } from '@xyflow/react';
type NumberNode = Node<{ number: number }, 'number'>;
type TextNode = Node<{ text: string }, 'text'>;
type AppNode = NumberNode | TextNode;
export default function CustomNode({ data }: NodeProps<AppNode>) {
if (data.type === 'number') {
return <div>A special number: {data.number}</div>;
}
return <div>A special text: {data.text}</div>;
}
Custom edges
For custom edges you have the same possiblity as for custom nodes.
import {
getStraightPath,
BaseEdge,
type EdgeProps,
type Edge,
} from '@xyflow/react';
type CustomEdge = Edge<{ value: number }, 'custom'>;
export default function CustomEdge({
id,
sourceX,
sourceY,
targetX,
targetY,
}: EdgeProps<CustomEdge>) {
const [edgePath] = getStraightPath({ sourceX, sourceY, targetX, targetY });
return <BaseEdge id={id} path={edgePath} />;
}
Advanced usage
When creating complex applications with React Flow, you will have a number of custom nodes & edges, each with different kinds of data attached to them. When we operate on these nodes & edges through built in functions and hooks, we have to make sure that we narrow down (opens in a new tab) the types of nodes & edges to prevent runtime errors.
Node
and Edge
type unions
You will see many functions, callbacks and hooks (even the ReactFlow component itself) that expect a NodeType
or EdgeType
generic. These generics are simply
unions (opens in a new tab) of all the different types of nodes & edges you have in your application.
As long as you have typed the data objects correctly (see previous section), you can use their exported type.
If you use any of the built-in nodes ('input', 'output', 'default') or edges
('straight', 'step', 'smoothstep', 'bezier'), you can add the BuiltInNode
and BuiltInEdge
types exported from @xyflow/react
to your union type.
import type { BuiltInNode, BuiltInEdge } from '@xyflow/react';
// Custom nodes
import NumberNode from './NumberNode';
import TextNode from './TextNode';
// Custom edge
import EditableEdge from './EditableEdge';
export type CustomNodeType = BuiltInNode | NumberNode | TextNode;
export type CustomEdgeType = BuiltInEdge | EditableEdge;
Functions passed to <ReactFlow />
To receive correct types for callback functions, you can pass your union types to the ReactFlow
component.
By doing that you will have to type your callback functions explicitly.
import { type OnNodeDrag } from '@xyflow/react';
// ...
// Pass your union type here ...
const onNodeDrag: OnNodeDrag<CustomNodeType> = useCallback((_, node) => {
if (node.type === 'number') {
// From here on, Typescript knows that node.data
// is of type { num: number }
console.log('drag event', node.data.number);
}
}, []);
const onNodesChange: OnNodesChange<CustomNodeType> = useCallback(
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes],
);
Hooks
The type unions can also be used to type the return values of many hooks.
import {
useReactFlow,
useHandleConnections,
useNodesData,
useStore,
} from '@xyflow/react';
export default function FlowComponent() {
// returned nodes and edges are correctly typed now
const { getNodes, getEdges } = useReactFlow<CustomNodeType, CustomEdgeType>();
// You can type useStore by typing the selector function
const nodes = useStore((s: ReactFlowState<CustomNodeType>) => ({
nodes: s.nodes,
}));
const connections = useHandleConnections({
type: 'target',
});
const nodesData = useNodesData<CustomNodeType>(connections?.[0].source);
nodeData.forEach(({ type, data }) => {
if (type === 'number') {
// This is type safe because we have narrowed down the type
console.log(data.number);
}
});
// ...
}
Type guards
There are multiple ways you can define type guards (opens in a new tab) in Typescript.
One way is to define type guard functions like isNumberNode
or isTextNode
to filter out specific nodes from a list of nodes.
function isNumberNode(node: CustomNodeType): node is NumberNode {
return node.type === 'number';
}
// numberNodes is of type NumberNode[]
const numberNodes = nodes.filter(isNumberNode);