Learn
Advanced Use
TypeScript

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:

  1. If you have multiple custom nodes, you want to pass a specific Node type as a generic to the NodeProps type:
NumberNode.tsx
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'>;
  1. 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 to NodeProps:
CustomNode.tsx
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.

CustomEdge.tsx
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.

FlowComponent.tsx
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);