# React Flow Documentation ## What is React Flow? React Flow is a library that allows you to create interactive, node-based user interfaces: flowcharts, diagrams, visual programming tools, and workflows inside your React applications. It supports theming, custom nodes and edges, a library of shadcn UI components, and offers a large collection of examples for rapid development. Developers can leverage the React Flow Pro platform for advanced features like real-time collaboration, complex layouts, and enhanced performance, making it suitable for both simple and large-scale, production-ready visual applications.## API Reference import { ContentGrid, ContentGridItem } from '@xyflow/xy-ui'; import { BlogPostPreview } from 'xy-shared'; ### API Reference This reference attempts to document every function, hook, component, and type exported by React Flow. If you are looking for guides and tutorials, please refer to our [learn section](/learn). #### How to use this reference We think that documentation should answer two broad questions: "what is this thing?" and "how do I use it?" To that end, our API reference aims to **concisely** answer that first question and learn section goes into more detail on the second. If you find yourself clicking around the reference wondering what the heck any of this means, maybe we have a guide that can help you out! #### A note for our long-term users If you're coming here from our old API pages things might look a bit different! We've reorganized our documentation to make it easier to look things up if you know what you're looking for. All our types, components, hooks, and util functions get their own page now to help you find exactly what you need. If you're new to React Flow or you're not sure where to look for something, take a look at the section below. #### A note for JavaScript users React Flow is written in TypeScript, but we know that not everyone uses it. We encourage developers to use the technology that works best for them, and throughout our documentation there is a blend of TypeScript and JavaScript examples. For our API reference, however, we use TypeScript's syntax to document the types of props and functions. Here's a quick crash course on how to read it: • `?` means that the field or argument is optional. • `` in a type definition represents a generic type parameter. Like a function argument but for types! The definition `type Array = ...` means a type called `Array` that takes a generic type parameter `T`. • `` when referring to a type is like "filling in" a generic type parameter. It's like calling a function but for types! The type `Array` is the type `Array` with the generic type parameter `T` filled in with the type `number`. • `T | U` means that the type is either `T` or `U`: this is often called a *union*. • `T & U` means that the type is both `T` and `U`: this is often called an *intersection*. The TypeScript folks have their own [handy guide for reading types](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) that you might find useful. If you're still stuck on something, feel free to drop by our [Discord](https://discord.com/invite/RVmnytFmGW) and ask for help! ### The ReactFlowProvider component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/components/ReactFlowProvider/index.tsx/#L9) The `` component is a [context provider](https://react.dev/learn/passing-data-deeply-with-context#) that makes it possible to access a flow's internal state outside of the [``](/api-reference/react-flow) component. Many of the hooks we provide rely on this component to work. ```tsx import { ReactFlow, ReactFlowProvider, useNodes } from '@xyflow/react' export default function Flow() { return ( ) } function Sidebar() { // This hook will only work if the component it's used in is a child of a // . const nodes = useNodes() return ( ) } ``` #### Props #### Notes * If you're using a router and want your flow's state to persist across routes, it's vital that you place the `` component *outside* of your router. * If you have multiple flows on the same page you will need to use a separate `` for each flow. ### The ReactFlow component import { ReactFlowAPIProps } from '@/references/ReactFlow.props'; ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/container/ReactFlow/index.tsx/#L47) The `` component is the heart of your React Flow application. It renders your nodes and edges, handles user interaction, and can manage its own state if used as an [uncontrolled flow](/learn/advanced-use/uncontrolled-flow). ```tsx import { ReactFlow } from '@xyflow/react' export default function Flow() { return } ``` This component takes a lot of different props, most of which are optional. We've tried to document them in groups that make sense to help you find your way. #### Common props These are the props you will most commonly use when working with React Flow. If you are working with a controlled flow with custom nodes, you will likely use almost all of these! #### Viewport props #### Edge props #### Event handlers > \[!WARNING] > > It's important to remember to define any event handlers outside of your > component or using React's `useCallback` hook. If you don't, this can cause > React Flow to enter an infinite re-render loop! ##### General Events ##### Node Events ##### Edge Events ##### Connection Events ##### Pane Events ##### Selection Events #### Interaction props #### Connection line props #### Keyboard props React Flow let's you pass in a few different keyboard shortcuts as another way to interact with your flow. We've tried to set up sensible defaults like using backspace to delete any selected nodes or edges, but you can use these props to set your own. To disable any of these shortcuts, pass in `null` to the prop you want to disable. #### Style props Applying certain classes to elements rendered inside the canvas will change how interactions are handled. These props let you configure those class names if you need to. #### Notes * The props of this component get exported as `ReactFlowProps` import { ApiReferenceSummary } from 'xy-shared/server'; ### Components ### The Background component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/additional-components/Background/Background.tsx) The `` component makes it convenient to render different types of backgrounds common in node-based UIs. It comes with three variants: `lines`, `dots` and `cross`. ```jsx import { useState } from 'react'; import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react'; export default function Flow() { return ( ); } ``` #### Props #### Examples ##### Combining multiple backgrounds It is possible to layer multiple `` components on top of one another to create something more interesting. The following example shows how to render a square grid accented every 10th line. ```tsx import { ReactFlow, Background, BackgroundVariant } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; export default function Flow() { return ( ); } ``` #### Notes * When combining multiple `` components it's important to give each of them a unique `id` prop! ### The BaseEdge component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/components/Edges/BaseEdge.tsx) The `` component gets used internally for all the edges. It can be used inside a custom edge and handles the invisible helper edge and the edge label for you. ```jsx import { BaseEdge } from '@xyflow/react'; export function CustomEdge({ sourceX, sourceY, targetX, targetY, ...props }) { const [edgePath] = getStraightPath({ sourceX, sourceY, targetX, targetY, }); const { label, labelStyle, markerStart, markerEnd, interactionWidth } = props; return ( ); } ``` #### Props #### Notes * If you want to use an edge marker with the [``](/api-reference/components/base-edge) component, you can pass the `markerStart` or `markerEnd` props passed to your custom edge through to the [``](/api-reference/components/base-edge) component. You can see all the props passed to a custom edge by looking at the [`EdgeProps`](/api-reference/types/edge-props) type. ### The ControlButton component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/additional-components/ControlButton.tsx) You can add buttons to the control panel by using the `` component and pass it as a child to the [``](/api-reference/components/controls) component. ```jsx import { MagicWand } from '@radix-ui/react-icons' import { ReactFlow, Controls, ControlButton } from '@xyflow/react' export default function Flow() { return ( alert('Something magical just happened. ✨')}> ) } ``` #### Props The `` component accepts any prop valid on a HTML `
{data.label}
); }; export default memo(CustomNode); ``` #### Props For TypeScript users, the props type for the `` component is exported as `NodeToolbarProps`. Additionally, the `` component accepts all props of the HTML `
` element. #### Notes * By default, the toolbar is only visible when a node is selected. If multiple nodes are selected it will not be visible to prevent overlapping toolbars or clutter. You can override this behavior by setting the `isVisible` prop to `true`. ### The Panel component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/components/Panel/index.tsx) The `` component helps you position content above the viewport. It is used internally by the [``](/api-reference/components/minimap) and [``](/api-reference/components/controls) components. ```jsx import { ReactFlow, Panel } from '@xyflow/react'; export default function Flow() { return ( top-left top-center top-right bottom-left bottom-center bottom-right center-left center-right ); } ``` #### Props For TypeScript users, the props type for the `` component is exported as `PanelProps`. Additionally, the `` component accepts all props of the HTML `
` element. ### The ViewportPortal component ### \ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/components/ViewportPortal/index.tsx) `` component can be used to add components to the same viewport of the flow where nodes and edges are rendered. This is useful when you want to render your own components that adhere to the same coordinate system as the nodes & edges and are also affected by zooming and panning ```jsx import React from 'react'; import { ViewportPortal } from '@xyflow/react'; export default function () { return (
This div is positioned at [100, 100] on the flow.
); } ``` #### Props import { ApiReferenceSummary } from 'xy-shared/server'; ### Hooks ### useConnection() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useConnection.ts) The `useConnection` hook returns the current connection state when there is an active connection interaction. If no connection interaction is active, it returns `null` for every property. A typical use case for this hook is to colorize handles based on a certain condition (e.g. if the connection is valid or not). ```tsx import { useConnection } from '@xyflow/react'; export default function App() { const connection = useConnection(); return (
{connection ? `Someone is trying to make a connection from ${connection.fromNode} to this one.` : 'There are currently no incoming connections!'}
); } ``` #### Signature ### useEdgesState() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodesEdgesState.ts) This hook makes it easy to prototype a controlled flow where you manage the state of nodes and edges outside the `ReactFlowInstance`. You can think of it like React's `useState` hook with an additional helper callback. ```jsx import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react'; const initialNodes = []; const initialEdges = []; export default function () { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); return ( ); } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom edge types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodes = useEdgesState(); ``` #### Notes * This hook was created to make prototyping easier and our documentation examples clearer. Although it is OK to use this hook in production, in practice you may want to use a more sophisticated state management solution like [Zustand](/docs/guides/state-management/) instead. ### useEdges() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useEdges.ts) This hook returns an array of the current edges. Components that use this hook will re-render **whenever any edge changes**. ```jsx import { useEdges } from '@xyflow/react'; export default function () { const edges = useEdges(); return
There are currently {edges.length} edges!
; } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom edge types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodes = useEdges(); ``` #### Notes * Relying on `useEdges` unnecessarily can be a common cause of performance issues. Whenever any edge changes, this hook will cause the component to re-render. Often we actually care about something more specific, like when the *number* of edges changes: where possible try to use [`useStore`](/api-reference/hooks/use-store) instead. ### useHandleConnections() > \[!WARNING] > > `useHandleConnections` is deprecated in favor of the more capable > [useNodeConnections](/api-reference/hooks/use-node-connections). [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useHandleConnections.ts) This hook returns an array connections on a specific handle or handle type. ```jsx import { useHandleConnections } from '@xyflow/react'; export default function () { const connections = useHandleConnections({ type: 'target', id: 'my-handle' }); return (
There are currently {connections.length} incoming connections!
); } ``` #### Signature ### useInternalNode() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useInternalNode.ts) This hook returns the internal representation of a specific node. Components that use this hook will re-render **whenever any node changes**, including when a node is selected or moved. ```jsx import { useInternalNode } from '@xyflow/react'; export default function () { const internalNode = useInternalNode('node-1'); const absolutePosition = internalNode.internals.positionAbsolute; return (
The absolute position of the node is at:

x: {absolutePosition.x}

y: {absolutePosition.y}

); } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom node types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const internalNode = useInternalNode(); ``` ### useKeyPress() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useKeyPress.ts) This hook lets you listen for specific key codes and tells you whether they are currently pressed or not. ```jsx import { useKeyPress } from '@xyflow/react'; export default function () { const spacePressed = useKeyPress('Space'); const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']); return (
{spacePressed &&

Space pressed!

} {cmdAndSPressed &&

Cmd + S pressed!

}
); } ``` #### Signature #### Notes * This hook does not rely on a `ReactFlowInstance` so you are free to use it anywhere in your app! ### useNodeConnections() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodeConnections.ts) This hook returns an array of connections on a specific node, handle type ('source', 'target') or handle ID. ```jsx import { useNodeConnections } from '@xyflow/react'; export default function () { const connections = useNodeConnections({ handleType: 'target', handleId: 'my-handle', }); return (
There are currently {connections.length} incoming connections!
); } ``` #### Signature ### useNodeId() [Source on Github](https://github.com/xyflow/xyflow/blob/v11/packages/core/src/contexts/NodeIdContext.ts/#L7) You can use this hook to get the id of the node it is used inside. It is useful if you need the node's id deeper in the render tree but don't want to manually drill down the id as a prop. ```js import { useNodeId } from '@xyflow/react'; export default function CustomNode() { return (
This node has an id of
); } function NodeIdDisplay() { const nodeId = useNodeId(); return {nodeId}; } ``` #### Signature #### Notes * This hook should only be used within a custom node or its children. ### useNodesData() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodesData.ts) This hook lets you subscribe to changes of a specific nodes `data` object. ```jsx import { useNodesData } from '@xyflow/react'; export default function () { const nodeData = useNodesData('nodeId-1'); const nodesData = useNodesData(['nodeId-1', 'nodeId-2']); } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom node types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodesData = useNodesData(['nodeId-1', 'nodeId-2']); ``` ### useNodesInitialized() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodesInitialized.ts) This hook tells you whether all the nodes in a flow have been measured and given a width and height. When you add a node to the flow, this hook will return `false` and then `true` again once the node has been measured. ```jsx import { useReactFlow, useNodesInitialized } from '@xyflow/react'; import { useEffect, useState } from 'react'; const options = { includeHiddenNodes: false, }; export default function useLayout() { const { getNodes } = useReactFlow(); const nodesInitialized = useNodesInitialized(options); const [layoutedNodes, setLayoutedNodes] = useState(getNodes()); useEffect(() => { if (nodesInitialized) { setLayoutedNodes(yourLayoutingFunction(getNodes())); } }, [nodesInitialized]); return layoutedNodes; } ``` #### Signature #### Notes * This hook always returns `false` if the internal nodes array is empty. ### useNodesState() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodesEdgesState.ts) This hook makes it easy to prototype a controlled flow where you manage the state of nodes and edges outside the `ReactFlowInstance`. You can think of it like React's `useState` hook with an additional helper callback. ```jsx import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react'; const initialNodes = []; const initialEdges = []; export default function () { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); return ( ); } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom node types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodes = useNodesState(); ``` #### Notes * This hook was created to make prototyping easier and our documentation examples clearer. Although it is OK to use this hook in production, in practice you may want to use a more sophisticated state management solution like [Zustand](/docs/guides/state-management/) instead. ### useNodes() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useNodes.ts) This hook returns an array of the current nodes. Components that use this hook will re-render **whenever any node changes**, including when a node is selected or moved. ```jsx import { useNodes } from '@xyflow/react'; export default function () { const nodes = useNodes(); return
There are currently {nodes.length} nodes!
; } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom node types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodes = useNodes(); ``` #### Notes * Relying on `useNodes` unnecessarily can be a common cause of performance issues. Whenever any node changes, this hook will cause the component to re-render. Often we actually care about something more specific, like when the *number* of nodes changes: where possible try to use [`useStore`](/api-reference/hooks/use-store) instead. ### useOnSelectionChange() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useOnSelectionChange.ts) This hook lets you listen for changes to both node and edge selection. As the name implies, the callback you provide will be called whenever the selection of *either* nodes or edges changes. > \[!WARNING] > > You need to memoize the passed `onChange` handler, otherwise the hook will not > work correctly. ```jsx import { useState } from 'react'; import { ReactFlow, useOnSelectionChange } from '@xyflow/react'; function SelectionDisplay() { const [selectedNodes, setSelectedNodes] = useState([]); const [selectedEdges, setSelectedEdges] = useState([]); // the passed handler has to be memoized, otherwise the hook will not work correctly const onChange = useCallback(({ nodes, edges }) => { setSelectedNodes(nodes.map((node) => node.id)); setSelectedEdges(edges.map((edge) => edge.id)); }, []); useOnSelectionChange({ onChange, }); return (

Selected nodes: {selectedNodes.join(', ')}

Selected edges: {selectedEdges.join(', ')}

); } ``` #### Signature #### Notes * This hook can only be used in a component that is a child of a [``](/api-reference/react-flow-provider) or a [``](/api-reference/react-flow) component. ### useOnViewportChange() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useOnViewportChange.ts) The `useOnViewportChange` hook lets you listen for changes to the viewport such as panning and zooming. You can provide a callback for each phase of a viewport change: `onStart`, `onChange`, and `onEnd`. ```tsx import { useCallback } from 'react'; import { useOnViewportChange } from '@xyflow/react'; function ViewportChangeLogger() { useOnViewportChange({ onStart: (viewport: Viewport) => console.log('start', viewport), onChange: (viewport: Viewport) => console.log('change', viewport), onEnd: (viewport: Viewport) => console.log('end', viewport), }); return null; } ``` #### Signature #### Notes * This hook can only be used in a component that is a child of a [``](/api-reference/react-flow-provider) or a [``](/api-reference/react-flow) component. ### useReactFlow() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useReactFlow.ts) This hook returns a [`ReactFlowInstance`](/api-reference/types/react-flow-instance) that can be used to update nodes and edges, manipulate the viewport, or query the current state of the flow. ```jsx import { useCallback, useState } from 'react'; import { useReactFlow } from '@xyflow/react'; export function NodeCounter() { const reactFlow = useReactFlow(); const [count, setCount] = useState(0); const countNodes = useCallback(() => { setCount(reactFlow.getNodes().length); // you need to pass it as a dependency if you are using it with useEffect or useCallback // because at the first render, it's not initialized yet and some functions might not work. }, [reactFlow]); return (

There are {count} nodes in the flow.

); } ``` #### Signature #### TypeScript This hook accepts a generic type argument of custom node & edge types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const reactFlow = useReactFlow(); ``` #### Notes * This hook can only be used in a component that is a child of a [``](/api-reference/react-flow-provider) or a [``](/api-reference/react-flow) component. * Unlike [`useNodes`](/api-reference/hooks/use-nodes) or [`useEdges`](/api-reference/hooks/use-edges), this hook won't cause your component to re-render when state changes. Instead, you can query the state when you need it by using methods on the [`ReactFlowInstance`](/api-reference/types/react-flow-instance) this hook returns. ### useStoreApi() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useStore.ts) In some cases, you might need to access the store directly. This hook returns the store object which can be used on demand to access the state or dispatch actions. > \[!NOTE] > > This hook should only be used if there is no other way to access the internal > state. For many of the common use cases, there are dedicated hooks available > such as [`useReactFlow`](/api-reference/hooks/use-react-flow), > [`useViewport`](/api-reference/hooks/use-viewport), etc. ```tsx import { useState, useCallback } from 'react'; import { ReactFlow, useStoreApi } from '@xyflow/react'; const NodesLengthDisplay = () => { const [nodesLength, setNodesLength] = useState(0); const store = useStoreApi(); const onClick = useCallback(() => { const { nodes } = store.getState(); const length = nodes.length || 0; setNodesLength(length); }, [store]); return (

The current number of nodes is: {nodesLength}

); }; function Flow() { return ( ); } ``` This example computes the number of nodes in the flow *on-demand*. This is in contrast to the example in the [`useStore`](/api-reference/hooks/use-store) hook that re-renders the component whenever the number of nodes changes. Choosing whether to calculate values on-demand or to subscribe to changes as they happen is a bit of a balancing act. On the one hand, putting too many heavy calculations in an event handler can make your app feel sluggish or unresponsive. On the other hand, computing values eagerly can lead to slow or unnecessary re-renders. We make both this hook and [`useStore`](/api-reference/hooks/use-store) available so that you can choose the approach that works best for your use-case. #### Signature #### TypeScript This hook accepts a generic type argument of custom node & edge types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const store = useStoreApi(); ``` import { Callout } from 'nextra/components'; ### useStore() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useStore.ts) This hook can be used to subscribe to internal state changes of the React Flow component. The `useStore` hook is re-exported from the [Zustand](https://github.com/pmndrs/zustand) state management library, so you should check out their docs for more details. This hook should only be used if there is no other way to access the internal state. For many of the common use cases, there are dedicated hooks available such as [`useReactFlow`](/api-reference/hooks/use-react-flow), [`useViewport`](/api-reference/hooks/use-viewport), etc. ```jsx import { ReactFlow, useStore } from '@xyflow/react'; const nodesLengthSelector = (state) => state.nodes.length || 0; const NodesLengthDisplay = () => { const nodesLength = useStore(nodesLengthSelector); return
The current number of nodes is: {nodesLength}
; }; function Flow() { return ( ); } ``` This example computes the number of nodes eagerly. Whenever the number of nodes in the flow changes, the `` component will re-render. This is in contrast to the example in the [`useStoreApi`](/api-reference/hooks/use-store-api) hook that only computes the number of nodes when a button is clicked. Choosing whether to calculate values on-demand or to subscribe to changes as they happen is a bit of a balancing act. On the one hand, putting too many heavy calculations in an event handler can make your app feel sluggish or unresponsive. On the other hand, computing values eagerly can lead to slow or unnecessary re-renders. We make both this hook and [`useStoreApi`](/api-reference/hooks/use-store-api) available so that you can choose the approach that works best for your use-case. #### Signature #### Examples ##### Triggering store actions You can manipulate the internal React Flow state by triggering internal actions through the `useStore` hook. These actions are already used internally throughout the library, but you can also use them to implement custom functionality. ```jsx import { useStore } from '@xyflow/react'; const setMinZoomSelector = (state) => state.setMinZoom; function MinZoomSetter() { const setMinZoom = useStore(setMinZoomSelector); return ; } ``` #### TypeScript This hook can be typed by typing the selector function. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const nodes = useStore((s: ReactFlowState) => s.nodes); ``` ### useUpdateNodeInternals() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useUpdateNodeInternals.ts) When you programmatically add or remove handles to a node or update a node's handle position, you need to let React Flow know about it using this hook. This will update the internal dimensions of the node and properly reposition handles on the canvas if necessary. ```jsx import { useCallback, useState } from 'react'; import { Handle, useUpdateNodeInternals } from '@xyflow/react'; export default function RandomHandleNode({ id }) { const updateNodeInternals = useUpdateNodeInternals(); const [handleCount, setHandleCount] = useState(0); const randomizeHandleCount = useCallback(() => { setHandleCount(Math.floor(Math.random() * 10)); updateNodeInternals(id); }, [id, updateNodeInternals]); return ( <> {Array.from({ length: handleCount }).map((_, index) => ( ))}

There are {handleCount} handles on this node.

); } ``` #### Signature #### Notes * This hook can only be used in a component that is a child of a [``](/api-reference/react-flow-provider) or a [``](/api-reference/react-flow) component. ### useViewport() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/hooks/useViewport.ts) The `useViewport` hook is a convenient way to read the current state of the [`Viewport`](/api-reference/types/viewport) in a component. Components that use this hook will re-render **whenever the viewport changes**. ```jsx import { useViewport } from '@xyflow/react'; export default function ViewportDisplay() { const { x, y, zoom } = useViewport(); return (

The viewport is currently at ({x}, {y}) and zoomed to {zoom}.

); } ``` #### Signature #### Notes * This hook can only be used in a component that is a child of a [``](/api-reference/react-flow-provider) or a [``](/api-reference/react-flow) component. import { ApiReferenceSummary } from 'xy-shared/server'; ### Types ### AriaLabelConfig [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/constants.ts/) With the `AriaLabelConfig` you can customize the aria labels used by React Flow. This is useful if you want to translate the labels or if you want to change them to better suit your application. #### Fields #### Default config ```js const defaultAriaLabelConfig = { 'node.a11yDescription.default': 'Press enter or space to select a node. Press delete to remove it and escape to cancel.', 'node.a11yDescription.keyboardDisabled': 'Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.', 'node.a11yDescription.ariaLiveMessage': ({ direction, x, y }: { direction: string; x: number; y: number }) => `Moved selected node ${direction}. New position, x: ${x}, y: ${y}`, 'edge.a11yDescription.default': 'Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.', // Control elements 'controls.ariaLabel': 'Control Panel', 'controls.zoomIn.ariaLabel': 'Zoom In', 'controls.zoomOut.ariaLabel': 'Zoom Out', 'controls.fitView.ariaLabel': 'Fit View', 'controls.interactive.ariaLabel': 'Toggle Interactivity', // Mini map 'minimap.ariaLabel': 'Mini Map', // Handle 'handle.ariaLabel': 'Handle', }; ``` ### BackgroundVariant [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/additional-components/Background/types.ts) The three variants are exported as an enum for convenience. You can either import the enum and use it like `BackgroundVariant.Lines` or you can use the raw string value directly. ```ts export enum BackgroundVariant { Lines = 'lines', Dots = 'dots', Cross = 'cross', } ``` ### ColorMode [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts) The `ColorMode` type defines the available color modes for the ReactFlow component. This can be used to control the theme of the flow diagram. ```ts export type ColorMode = 'light' | 'dark' | 'system'; ``` ### ConnectionLineComponentProps [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L193) If you want to render a custom component for connection lines, you can set the `connectionLineComponent` prop on the [``](/api-reference/react-flow#connection-connectionLineComponent) component. The `ConnectionLineComponentProps` are passed to your custom component. #### Props ### ConnectionLineComponent [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts#L265) The `ConnectionLineComponent` type allows you to provide a custom React component to render the connection line when users create new edges. The component receives `ConnectionLineComponentProps` as its props. ```ts type ConnectionLineComponent = React.ComponentType; ``` ### ConnectionLineType [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/edges.ts/#L62) If you set the `connectionLineType` prop on your [``](/api-reference/react-flow#connection-connectionLineType) component, it will dictate the style of connection line rendered when creating new edges. ```ts export enum ConnectionLineType { Bezier = 'default', Straight = 'straight', Step = 'step', SmoothStep = 'smoothstep', SimpleBezier = 'simplebezier', } ``` #### Notes * If you choose to render a custom connection line component, this value will be passed to your component as part of its [`ConnectionLineComponentProps`](/api-reference/types/connection-line-component-props). ### ConnectionMode [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L68) The `ConnectionMode` enum provides two options for connection behavior in React Flow: * `Strict`: Connections can only be made starting from a source handle and ending on a target handle * `Loose`: Connections can be made between any handles, regardless of type ```ts enum ConnectionMode { Strict = 'strict', Loose = 'loose', } ``` ### ConnectionState [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L148-L174) The `ConnectionState` type bundles all information about an ongoing connection. It is returned by the [`useConnection`](/api-reference/hooks/use-connection) hook. ```ts type NoConnection = { inProgress: false; isValid: null; from: null; fromHandle: null; fromPosition: null; fromNode: null; to: null; toHandle: null; toPosition: null; toNode: null; }; type ConnectionInProgress = { inProgress: true; isValid: boolean | null; from: XYPosition; fromHandle: Handle; fromPosition: Position; fromNode: NodeBase; to: XYPosition; toHandle: Handle | null; toPosition: Position; toNode: NodeBase | null; }; type ConnectionState = ConnectionInProgress | NoConnection; ``` #### Fields ### Connection [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L29-L34) The `Connection` type is the basic minimal description of an [`Edge`](/api-reference/types/edge) between two nodes. The [`addEdge`](/api-reference/utils/add-edge) util can be used to upgrade a `Connection` to an [`Edge`](/api-reference/types/edge). #### Fields ### CoordinateExtent [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/utils.ts/#L36-L37) A coordinate extent represents two points in a coordinate system: one in the top left corner and one in the bottom right corner. It is used to represent the bounds of nodes in the flow or the bounds of the viewport. ```ts export type CoordinateExtent = [[number, number], [number, number]]; ``` #### Notes * Props that expect a `CoordinateExtent` usually default to `[[-∞, -∞], [+∞, +∞]]` to represent an unbounded extent. ### DefaultEdgeOptions [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L88-L89) Many properties on an [`Edge`](/api-reference/types/edge) are optional. When a new edge is created, the properties that are not provided will be filled in with the default values passed to the `defaultEdgeOptions` prop of the [``](/api-reference/react-flow#defaultedgeoptions) component. #### Fields ### DeleteElements DeleteElements deletes provided nodes and edges and handles deleting any connected edges as well as child nodes. Returns successfully deleted edges and nodes asynchronously. ```ts export type DeleteElements = (payload: { nodes?: (Partial & { id: Node['id'] })[]; edges?: (Partial & { id: Edge['id'] })[]; }) => Promise<{ deletedNodes: Node[]; deletedEdges: Edge[]; }>; ``` ### EdgeChange [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/changes.ts/#L68-L72) The [`onEdgesChange`](/api-reference/react-flow#on-edges-change) callback takes an array of `EdgeChange` objects that you should use to update your flow's state. The `EdgeChange` type is a union of four different object types that represent that various ways an edge can change in a flow. ```ts export type EdgeChange = | EdgeAddChange | EdgeRemoveChange | EdgeReplaceChange | EdgeSelectionChange; ``` #### Variants ##### EdgeAddChange ##### EdgeRemoveChange ##### EdgeReplaceChange ##### EdgeSelectionChange ### EdgeMarker [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/edges.ts/#L70-L78) Edges can optionally have markers at the start and end of an edge. The `EdgeMarker` type is used to configure those markers! Check the docs for [`MarkerType`](/api-reference/types/marker-type) for details on what types of edge marker are available. #### Fields ### EdgeMouseHandler [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts#L81) The `EdgeMouseHandler` type defines the callback function that is called when mouse events occur on an edge. This callback receives the event and the edge that triggered it. ```ts type EdgeMouseHandler = (event: React.MouseEvent, edge: Edge) => void; ``` ### EdgeProps [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L100) When you implement a custom edge it is wrapped in a component that enables some basic functionality. The `EdgeProps` type takes a generic parameter to specify the type of edges you use in your application: ```ts type AppEdgeProps = EdgeProps; ``` Your custom edge component receives the following props: #### Fields ### EdgeTypes [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L76) The `EdgeTypes` type is used to define custom edge types. Each key in the object represents an edge type, and the value is the component that should be rendered for that type. ```ts export type EdgeTypes = { [key: string]: React.ComponentType; }; ``` ### Edge [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L34-L353) Where a [`Connection`](/api-reference/types/connection) is the minimal description of an edge between two nodes, an `Edge` is the complete description with everything React Flow needs to know in order to render it. ```ts export type Edge = DefaultEdge | SmoothStepEdge | BezierEdge; ``` #### Variants ##### Edge [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L34-L353) ##### SmoothStepEdge [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L45-L46) The `SmoothStepEdge` variant has all the same fields as an `Edge`, but it also has the following additional fields: {/* type SmoothStepEdge isn't exported, and conflicts with SmoothStepEdge component name */} ##### BezierEdge [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/edges.ts/#L52-L53) The `BezierEdge` variant has all the same fields as an `Edge`, but it also has the following additional fields: {/* type BezierEdge isn't exported, and conflicts with BezierEdge component name */} #### Default edge types You can create any of React Flow's default edges by setting the `type` property to one of the following values: * `"default"` * `"straight"` * `"step"` * `"smoothstep"` * `"simplebezier"` If you don't set the `type` property at all, React Flow will fallback to the `"default"` bezier curve edge type. These default edges are available even if you set the [`edgeTypes`](/api-reference/react-flow#edge-types) prop to something else, unless you override any of these keys directly. ### FitViewOptions [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts/#L67-L68) When calling [`fitView`](/api-reference/types/react-flow-instance#fitview) these options can be used to customize the behavior. For example, the `duration` option can be used to transform the viewport smoothly over a given amount of time. #### Fields ### HandleConnection [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L36-L37) The `HandleConnection` type is an extension of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`. #### Fields ### Handle [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/handles.ts/#L5) The `Handle` type represents the attributes of a handle. #### Fields ### InternalNode [Source on GitHub](https://github.com/xyflow/xyflow/blob/99985b52026cf4ac65a1033178cf8c2bea4e14fa/packages/system/src/types/nodes.ts#L68) The `InternalNode` type is identical to the base [`Node`](/api-references/types/node) type but is extended with some additional properties used internally by React Flow. Some functions and callbacks that return nodes may return an `InternalNode`. #### Fields ### IsValidConnection [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L212) The `IsValidConnection` type represents a function that validates whether a connection between nodes is allowed. It receives a [`Connection`](/api-reference/types/connection) and returns a boolean indicating whether the connection is valid and therefore should be created. ```ts type IsValidConnection = (edge: Edge | Connection) => boolean; ``` ### KeyCode [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L155) The `KeyCode` type is used to specify keyboard key codes or combinations, such as deleting nodes or enabling multi-selection. It can be a single string or an array of strings representing key codes. ```ts type KeyCode = string | Array; ``` ### MarkerType [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/edges.ts/#L82-L83) Edges may optionally have a marker on either end. The MarkerType type enumerates the options available to you when configuring a given marker. ```ts export enum MarkerType { Arrow = 'arrow', ArrowClosed = 'arrowclosed', } ``` ### MiniMapNodeProps [Source on GitHub](https://github.com/xyflow/xyflow/blob/487b13c9ad8903789f56c6fcfd8222f9cb74b812/packages/react/src/additional-components/MiniMap/types.ts/#L60) The MiniMapNodeProps type defines the props for nodes in the MiniMap component. This is only relevant if you pass a custom node type to the MiniMap. #### Fields ``` ``` ### NodeChange [Source on GitHub](https://github.com/xyflow/xyflow/blob/487b13c9ad8903789f56c6fcfd8222f9cb74b812/packages/system/src/types/changes.ts/#L47) The [`onNodesChange`](/api-reference/react-flow#on-nodes-change) callback takes an array of `NodeChange` objects that you should use to update your flow's state. The `NodeChange` type is a union of six different object types that represent that various ways an node can change in a flow. ```ts export type NodeChange = | NodeDimensionChange | NodePositionChange | NodeSelectionChange | NodeRemoveChange | NodeAddChange | NodeReplaceChange; ``` #### Variant types ##### NodeDimensionChange ##### NodePositionChange ##### NodeSelectionChange ##### NodeRemoveChange ##### NodeAddChange ##### NodeReplaceChange ### NodeConnection [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L36-L37) The `NodeConnection` type is an extension of a basic [Connection](/api-reference/types/connection) that includes the `edgeId`. #### Fields ### NodeHandle [Source on GitHub](https://github.com/xyflow/xyflow/blob/13897512d3c57e72c2e27b14ffa129412289d948/packages/system/src/types/nodes.ts#L139) The `NodeHandle` type is used to define a handle for a node if server-side rendering is used. On the server, React Flow can't measure DOM nodes, so it's necessary to define the handle position dimensions. ### NodeMouseHandler [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/nodes.tsL32) The `NodeMouseHandler` type defines the callback function that is called when mouse events occur on a node. This callback receives the event and the node that triggered it. ```ts export type NodeMouseHandler = (event: React.MouseEvent, node: Node) => void; ``` ### NodeOrigin The origin of a Node determines how it is placed relative to its own coordinates. `[0, 0]` places it at the top left corner, `[0.5, 0.5]` right in the center and `[1, 1]` at the bottom right of its position. ```ts export type NodeOrigin = [number, number]; ``` ### NodeProps ### NodeProps\ [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/nodes.ts/#L89) When you implement a [custom node](/learn/customization/custom-nodes) it is wrapped in a component that enables basic functionality like selection and dragging. #### Usage ```tsx import { useState } from 'react'; import { NodeProps, Node } from '@xyflow/react'; export type CounterNode = Node< { initialCount?: number; }, 'counter' >; export default function CounterNode(props: NodeProps) { const [count, setCount] = useState(props.data?.initialCount ?? 0); return (

Count: {count}

); } ``` Remember to register your custom node by adding it to the [`nodeTypes`](/api-reference/react-flow#nodetypes) prop of your `` component. ```tsx import { ReactFlow } from '@xyflow/react'; import CounterNode from './CounterNode'; const nodeTypes = { counterNode: CounterNode, }; export default function App() { return } ``` You can read more in our [custom node guide](/learn/customization/custom-nodes). #### Fields Your custom node receives the following props: ### NodeTypes [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/nodes.ts) The `NodeTypes` type is used to define custom node types. Each key in the object represents a node type, and the value is the component that should be rendered for that type. ```ts type NodeTypes = { [key: string]: React.ComponentType; }; ``` ### Node [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/nodes.ts/#L10) The `Node` type represents everything React Flow needs to know about a given node. Many of these properties can be manipulated both by React Flow or by you, but some such as `width` and `height` should be considered read-only. #### Fields #### Default node types You can create any of React Flow's default nodes by setting the `type` property to one of the following values: * `"default"` * `"input"` * `"output"` * `"group"` If you don't set the `type` property at all, React Flow will fallback to the `"default"` node with both an input and output port. These default nodes are available even if you set the [`nodeTypes`](/api-reference/react-flow#node-types) prop to something else, unless you override any of these keys directly. #### Notes * You shouldn't try to set the `width` or `height` of a node directly. It is calculated internally by React Flow and used when rendering the node in the viewport. To control a node's size you should use the `style` or `className` props to apply CSS styles instead. ### OnBeforeDelete [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L207) The `OnBeforeDelete` type defines the callback function that is called before nodes or edges are deleted. This callback receives an object containing the nodes and edges that are about to be deleted. ```ts type OnBeforeDelete = (params: { nodes: Node[]; edges: Edge[]; }) => Promise; ``` ### OnConnectEnd [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L89) The `OnConnectEnd` type represents a callback function that is called when finishing or canceling a connection attempt. It receives the mouse or touch event and the final state of the connection attempt. ```ts type OnConnectEnd = ( event: MouseEvent | TouchEvent, connectionState: FinalConnectionState, ) => void; ``` ### OnConnectStart [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L79) The `OnConnectStart` type represents a callback function that is called when starting to create a connection between nodes. It receives the mouse or touch event and information about the source node and handle. ```ts type OnConnectStart = ( event: MouseEvent | TouchEvent, params: OnConnectStartParams, ) => void; ``` ### OnConnect [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L80) The `OnConnect` type represents a callback function that is called when a new connection is created between nodes. It receives a [`Connection`](/api-reference/types/connection) containing the source and target node IDs and their respective handle IDs. ```ts type OnConnect = (connection: Connection) => void; ``` ### OnDelete [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L59) The `OnDelete` type defines the callback function that is called when nodes or edges are deleted. This callback receives an object containing the deleted nodes and edges. ```ts type OnDelete = (params: { nodes: Node[]; edges: Edge[] }) => void; ``` ### OnEdgesChange This type is used for typing the [`onEdgesChange`](/api-reference/react-flow#on-edges-change) function. ```tsx export type OnEdgesChange = ( changes: EdgeChange[], ) => void; ``` #### Fields #### Usage This type accepts a generic type argument of custom edge types. See this [section in our Typescript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const onEdgesChange: OnEdgesChange = useCallback( (changes) => setEdges((eds) => applyEdgeChanges(changes, eds)), [setEdges], ); ``` ### OnEdgesDelete [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L52) The `OnEdgesDelete` type defines the callback function that is called when edges are deleted. This callback receives an array of the deleted edges. ```ts type OnEdgesDelete = (edges: Edge[]) => void; ``` ### OnError [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L233) The `OnError` type defines the callback function that is called when an error occurs. This callback receives an error id and the error message as its argument. ```ts type OnError = (id: string, error: string) => void; ``` ### OnInit [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L113) The `OnInit` type defines the callback function that is called when the ReactFlow instance is initialized. This callback receives the ReactFlow instance as its argument. ```ts type OnInit = (reactFlowInstance: ReactFlowInstance) => void; ``` ### OnMove [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L16) The `OnMove` type is a callback that fires whenever the viewport is moved, either by user interaction or programmatically. It receives the triggering event and the new viewport state. ```ts type OnMove = (event: MouseEvent | TouchEvent | null, viewport: Viewport) => void; ``` ### OnNodeDrag [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/nodes.ts#L34) The `OnNodeDrag` type defines the callback function that is called when a node is being dragged. This callback receives the event and the node that is being dragged. ```ts type OnNodeDrag = (event: React.MouseEvent, node: Node) => void; ``` ### OnNodesChange This type is used for typing the [`onNodesChange`](/api-reference/react-flow#on-nodes-change) function. ```tsx export type OnNodesChange = ( changes: NodeChange[], ) => void; ``` #### Fields #### Usage This type accepts a generic type argument of custom nodes types. See this [section in our TypeScript guide](/learn/advanced-use/typescript#nodetype-edgetype-unions) for more information. ```tsx const onNodesChange: OnNodesChange = useCallback( (changes) => setNodes((nds) => applyNodeChanges(changes, nds)), [setNodes], ); ``` ### OnNodesDelete [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#L51) The `OnNodesDelete` type defines the callback function that is called when nodes are deleted. This callback receives an array of the deleted nodes. ```ts type OnNodesDelete = (nodes: Node[]) => void; ``` ### OnReconnect [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L83) The `OnReconnect` type represents a callback function that is called when an existing edge is reconnected to a different node or handle. It receives the old edge and the new connection details. ```ts type OnReconnect = ( oldEdge: EdgeType, newConnection: Connection, ) => void; ``` ### OnSelectionChangeFunc [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/general.ts#98) The `OnSelectionChangeFunc` type is a callback that is triggered when the selection of nodes or edges changes. It receives an object containing the currently selected nodes and edges. ```ts type OnSelectionChangeFunc = (params: { nodes: Node[]; edges: Edge[] }) => void; ``` ### PanOnScrollMode [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L166) The `PanOnScrollMode` enum controls the panning behavior of the viewport when the user scrolls. Choose `Free` for unrestricted panning, `Vertical` for up-and-down only, or `Horizontal` for left-and-right only. ```ts enum PanOnScrollMode { Free = 'free', Vertical = 'vertical', Horizontal = 'horizontal', } ``` ### PanelPosition [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L111-L112) This type is mostly used to help position things on top of the flow viewport. For example both the [``](/api-reference/components/minimap) and [``](/api-reference/components/controls) components take a `position` prop of this type. ```ts export type PanelPosition = | 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center-left' | 'center-right'; ``` ### Position [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/utils.ts/#L1) While [`PanelPosition`](/api-reference/types/panel-position) can be used to place a component in the corners of a container, the `Position` enum is less precise and used primarily in relation to edges and handles. ```ts export enum Position { Left = 'left', Top = 'top', Right = 'right', Bottom = 'bottom', } ``` ### ProOptions By default, we render a small attribution in the corner of your flows that links back to the project. Anyone is free to remove this attribution whether they're a Pro subscriber or not but we ask that you take a quick look at our [removing attribution](/learn/troubleshooting/remove-attribution) guide before doing so. import { NodesAndEdgesFields, IntersectionFields, ViewportFields, } from '@/references/react-flow-instance'; ### ReactFlowInstance [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/instance.ts/#L178-L179) The `ReactFlowInstance` provides a collection of methods to query and manipulate the internal state of your flow. You can get an instance by using the [`useReactFlow`](/api-reference/hooks/use-react-flow) hook or attaching a listener to the [`onInit`](/api-reference/react-flow#event-oninit) event. #### Fields ##### Nodes and edges ##### Intersections ##### Viewport ### ReactFlowJsonObject [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/instance.ts/#L5) A JSON-compatible representation of your flow. You can use this to save the flow to a database for example and load it back in later. #### Fields ### Rect [Source on GitHub](https://github.com/xyflow/xyflow/blob/f0ce2c876d8688e13632bc86286cf857f86dead6/packages/system/src/types/utils.ts/#L39-L40) The `Rect` type defines a rectangle in a two-dimensional space with dimensions and a position. ### ResizeParams [Source on Github](https://github.com/xyflow/xyflow/blob/v11/packages/node-resizer/src/types.ts/#L4) The `ResizeParams` type is used to type the various events that are emitted by the `` component. You'll sometimes see this type extended with an additional direction field too. #### Fields ### SelectionDragHandler [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/types/nodes.ts#L33) The `SelectionDragHandler` type is a callback for handling drag events involving selected nodes. It receives the triggering mouse or touch event and an array of the affected nodes. ```ts type SelectionDragHandler = ( event: ReactMouseEvent, nodes: NodeType[], ) => void; ``` ### SelectionMode [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L223) The `SelectionMode` enum provides two options for node selection behavior: * `Full`: A node is only selected when the selection rectangle fully contains it * `Partial`: A node is selected when the selection rectangle partially overlaps with it ```ts enum SelectionMode { Partial = 'partial', Full = 'full', } ``` ### SnapGrid [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts#L157) The `SnapGrid` type defines the grid size for snapping nodes on the pane. It is used in conjunction with the `snapToGrid` prop to enable grid snapping functionality. ```ts type SnapGrid = [number, number]; ``` ### Viewport [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/types/general.ts/#L149-L153) Internally, React Flow maintains a coordinate system that is independent of the rest of the page. The `Viewport` type tells you where in that system your flow is currently being display at and how zoomed in or out it is. #### Fields #### Notes * A `Transform` has the same properties as the viewport, but they represent different things. Make sure you don't get them muddled up or things will start to look weird! ### XYPosition All positions are stored in an object with x and y coordinates. ```ts export type XYPosition = { x: number; y: number; }; ``` import { ApiReferenceSummary } from 'xy-shared/server'; ### Utils ### addEdge() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/edges/general.ts/#L100) This util is a convenience function to add a new [`Edge`](/api-reference/types/edge) to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. ```js import { useCallback } from 'react'; import { ReactFlow, addEdge, useNodesState, useEdgesState, } from '@xyflow/react'; export default function Flow() { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const onConnect = useCallback( (connection) => { setEdges((oldEdges) => addEdge(connection, oldEdges)); }, [setEdges], ); return ; } ``` #### Signature #### Notes * If an edge with the same `target` and `source` already exists (and the same `targetHandle` and `sourceHandle` if those are set), then this util won't add a new edge even if the `id` property is different. ### applyEdgeChanges() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/utils/changes.ts/#L167) Various events on the [``](/api-reference/react-flow) component can produce an [`EdgeChange`](/api-reference/types/edge-change) that describes how to update the edges of your flow in some way. If you don't need any custom behavior, this util can be used to take an array of these changes and apply them to your edges. ```js import { useState, useCallback } from 'react'; import { ReactFlow, applyEdgeChanges } from '@xyflow/react'; export default function Flow() { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const onEdgesChange = useCallback( (changes) => { setEdges((oldEdges) => applyEdgeChanges(changes, oldEdges)); }, [setEdges], ); return ( ); } ``` #### Signature #### Notes * If you don't need any custom behavior, the [`useEdgesState`](/api-reference/hooks/use-edges-state) hook conveniently wraps this util and React's `useState` hook for you and might be simpler to use. ### applyNodeChanges() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/react/src/utils/changes.ts/#L140) Various events on the [``](/api-reference/react-flow) component can produce a [`NodeChange`](/api-reference/types/node-change) that describes how to update the nodes of your flow in some way. If you don't need any custom behavior, this util can be used to take an array of these changes and apply them to your nodes. ```js import { useState, useCallback } from 'react'; import { ReactFlow, applyNodeChanges } from '@xyflow/react'; export default function Flow() { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const onNodesChange = useCallback( (changes) => { setNodes((oldNodes) => applyNodeChanges(changes, oldNodes)); }, [setNodes], ); return ( ); } ``` #### Signature #### Notes * If you don't need any custom behavior, the [`useNodesState`](/api-reference/hooks/use-nodes-state) hook conveniently wraps this util and React's `useState` hook for you and might be simpler to use. ### getBezierPath() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/edges/bezier-edge.ts/#L95) The `getBezierPath` util returns everything you need to render a bezier edge between two nodes. ```js import { Position, getBezierPath } from '@xyflow/react'; const source = { x: 0, y: 20 }; const target = { x: 150, y: 100 }; const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({ sourceX: source.x, sourceY: source.y, sourcePosition: Position.Right, targetX: target.x, targetY: target.y, targetPosition: Position.Left, }); console.log(path); //=> "M0,20 C75,20 75,100 150,100" console.log(labelX, labelY); //=> 75, 60 console.log(offsetX, offsetY); //=> 75, 40 ``` #### Signature #### Notes * This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. ### getConnectedEdges() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L224) This utility filters an array of edges, keeping only those where either the source or target node is present in the given array of nodes. ```js import { getConnectedEdges } from '@xyflow/react'; const nodes = [ { id: 'a', position: { x: 0, y: 0 } }, { id: 'b', position: { x: 100, y: 0 } }, ]; const edges = [ { id: 'a->c', source: 'a', target: 'c' }, { id: 'c->d', source: 'c', target: 'd' }, ]; const connectedEdges = getConnectedEdges(nodes, edges); // => [{ id: 'a->c', source: 'a', target: 'c' }] ``` #### Signature ### getIncomers() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L91) This util is used to tell you what nodes, if any, are connected to the given node as the *source* of an edge. ```ts import { getIncomers } from '@xyflow/react'; const nodes = []; const edges = []; const incomers = getIncomers( { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } }, nodes, edges, ); ``` #### Signature ### getNodesBounds() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L133) Returns the bounding box that contains all the given nodes in an array. This can be useful when combined with [`getViewportForBounds`](/api-reference/utils/get-viewport-for-bounds) to calculate the correct transform to fit the given nodes in a viewport. > \[!NOTE] > > This function was previously called `getRectOfNodes` ```js import { getNodesBounds } from '@xyflow/react'; const nodes = [ { id: 'a', position: { x: 0, y: 0 }, data: { label: 'a' }, width: 50, height: 25, }, { id: 'b', position: { x: 100, y: 100 }, data: { label: 'b' }, width: 50, height: 25, }, ]; const bounds = getNodesBounds(nodes); ``` #### Signature ### getOutgoers() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L64) This util is used to tell you what nodes, if any, are connected to the given node as the *target* of an edge. ```ts import { getOutgoers } from '@xyflow/react'; const nodes = []; const edges = []; const outgoers = getOutgoers( { id: '1', position: { x: 0, y: 0 }, data: { label: 'node' } }, nodes, edges, ); ``` #### Signature ### getSimpleBezierPath() [Source on Github](https://github.com/xyflow/xyflow/blob/main/packages/react/src/components/Edges/SimpleBezierEdge.tsx/#L32) The `getSimpleBezierPath` util returns everything you need to render a simple bezier edge between two nodes. ```js import { Position, getSimpleBezierPath } from '@xyflow/react'; const source = { x: 0, y: 20 }; const target = { x: 150, y: 100 }; const [path, labelX, labelY, offsetX, offsetY] = getSimpleBezierPath({ sourceX: source.x, sourceY: source.y, sourcePosition: Position.Right, targetX: target.x, targetY: target.y, targetPosition: Position.Left, }); console.log(path); //=> "M0,20 C75,20 75,100 150,100" console.log(labelX, labelY); //=> 75, 60 console.log(offsetX, offsetY); //=> 75, 40 ``` #### Signature #### Notes * This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. ### getSmoothStepPath() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/edges/smoothstep-edge.ts/#L215) The `getSmoothStepPath` util returns everything you need to render a stepped path between two nodes. The `borderRadius` property can be used to choose how rounded the corners of those steps are. ```js import { Position, getSmoothStepPath } from '@xyflow/react'; const source = { x: 0, y: 20 }; const target = { x: 150, y: 100 }; const [path, labelX, labelY, offsetX, offsetY] = getSmoothStepPath({ sourceX: source.x, sourceY: source.y, sourcePosition: Position.Right, targetX: target.x, targetY: target.y, targetPosition: Position.Left, }); console.log(path); //=> "M0 20L20 20L 70,20Q 75,20 75,25L 75,95Q ..." console.log(labelX, labelY); //=> 75, 60 console.log(offsetX, offsetY); //=> 75, 40 ``` #### Signature #### Notes * This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. * You can set the `borderRadius` property to `0` to get a step edge path. ### getStraightPath() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/edges/straight-edge.ts/#L30) Calculates the straight line path between two points. ```js import { getStraightPath } from '@xyflow/react'; const source = { x: 0, y: 20 }; const target = { x: 150, y: 100 }; const [path, labelX, labelY, offsetX, offsetY] = getStraightPath({ sourceX: source.x, sourceY: source.y, targetX: target.x, targetY: target.y, }); console.log(path); //=> "M 0,20L 150,100" console.log(labelX, labelY); //=> 75, 60 console.log(offsetX, offsetY); //=> 75, 40 ``` #### Signature #### Notes * This function returns a tuple (aka a fixed-size array) to make it easier to work with multiple edge paths at once. import { Callout } from 'nextra/components'; ### getViewportForBounds() [Source on Github](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/general.ts/#L170) This util returns the viewport for the given bounds. You might use this to pre-calculate the viewport for a given set of nodes on the server or calculate the viewport for the given bounds *without* changing the viewport directly. > \[!NOTE] > > This function was previously called `getTransformForBounds` ```js import { getViewportForBounds } from '@xyflow/react'; const { x, y, zoom } = getViewportForBounds( { x: 0, y: 0, width: 100, height: 100, }, 1200, 800, 0.5, 2, ); ``` #### Signature #### Notes * This is quite a low-level utility. You might want to look at the [`fitView`](/api-reference/types/react-flow-instance#fitview) or [`fitBounds`](/api-reference/types/react-flow-instance#fitbounds) methods for a more practical api. ### isEdge() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L39-L40) Test whether an object is usable as an [`Edge`](/api-reference/types/edge). In TypeScript this is a type guard that will narrow the type of whatever you pass in to [`Edge`](/api-reference/types/edge) if it returns `true`. ```js import { isEdge } from '@xyflow/react'; const edge = { id: 'edge-a', source: 'a', target: 'b', }; if (isEdge(edge)) { // ... } ``` #### Signature ### isNode() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/system/src/utils/graph.ts/#L49) Test whether an object is usable as a [`Node`](/api-reference/types/node). In TypeScript this is a type guard that will narrow the type of whatever you pass in to [`Node`](/api-reference/types/node) if it returns `true`. ```js import { isNode } from '@xyflow/react'; const node = { id: 'node-a', data: { label: 'node', }, position: { x: 0, y: 0, }, }; if (isNode(node)) { // .. } ``` #### Signature ### reconnectEdge() [Source on GitHub](https://github.com/xyflow/xyflow/blob/main/packages/core/src/utils/graph.ts/#L97) A handy utility to update an existing [`Edge`](/api-reference/types/edge) with new properties. This searches your edge array for an edge with a matching `id` and updates its properties with the connection you provide. ```js const onReconnect = useCallback( (oldEdge: Edge, newConnection: Connection) => setEdges((els) => reconnectEdge(oldEdge, newConnection, els)), [] ); ``` #### Signature ## Learn import { Emoji } from '@xyflow/xy-ui'; import { Callout, Cards, Steps } from 'nextra/components'; import { LiteYouTubeEmbed } from 'xy-shared'; import { RemoteCodeViewer } from 'xy-shared/server'; ### Quick Start This page will take you from zero to a working React Flow app in a few minutes. If you just want to have a look around and get an impression of React Flow, check out our interactive no-code [Playground](https://play.reactflow.dev/). If you are a large language model, you can find the raw text of React Flow's documentation in the [llms.txt](/llms.txt) file. #### Installation First, spin up a new React project however you like -- we recommend using [Vite](https://vitejs.dev/) ```bash copy npm2yarn npm init vite my-react-flow-app -- --template react ``` Next `cd` into your new project folder and add [`@xyflow/react`](https://npmjs.com/package/@xyflow/react) as a dependency ```bash copy npm2yarn npm install @xyflow/react ``` Lastly, spin up the dev server and you're good to go! #### Usage We will render the [``](/api-reference/react-flow#reactflow) component from the `@xyflow/react` package. That and defining a handful of [node](/api-reference/types/node) objects, [edge](/api-reference/types/edge) objects and [event handlers](/api-reference/react-flow#event-handlers) are all we need to get something going! Get rid of everything inside `App.jsx` and add the following: ```jsx "import '@xyflow/react/dist/style.css';" "width: '100vw', height: '100vh'" import { useState, useCallback } from 'react'; import { ReactFlow, applyNodeChanges, applyEdgeChanges, addEdge } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; const initialNodes = [ { id: 'n1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } }, { id: 'n2', position: { x: 0, y: 100 }, data: { label: 'Node 2' } }, ]; const initialEdges = [{ id: 'n1-n2', source: 'n1', target: 'n2' }]; export default function App() { const [nodes, setNodes] = useState(initialNodes); const [edges, setEdges] = useState(initialEdges); const onNodesChange = useCallback( (changes) => setNodes((nodesSnapshot) => applyNodeChanges(changes, nodesSnapshot)), [], ); const onEdgesChange = useCallback( (changes) => setEdges((edgesSnapshot) => applyEdgeChanges(changes, edgesSnapshot)), [], ); const onConnect = useCallback( (params) => setEdges((edgesSnapshot) => addEdge(params, edgesSnapshot)), [], ); return (
); } ``` There are two things to pay attention to here: * You must import the css stylesheet for React Flow to work. * The `` component must have a parent element with a width and height. #### Result Et voila. You've already created your first interactive flow! #### Next steps import { Callout } from 'nextra/components'; ### Accessibility If you have suggestions for how we can improve the accessibility of React Flow, please feel free to [contact us](https://xyflow.com/contact). React Flow provides keyboard and screen-reader support to help meet accessibility standards. By default, all nodes and edges are keyboard-focusable and operable. You can enable or disable these features with props including: [nodesFocusable](/api-reference/react-flow#nodesfocusable), [edgesFocusable](/api-reference/react-flow#edgesfocusable), and [disableKeyboardA11y](/api-reference/react-flow#disablekeyboarda11y) which are passed through `` like this: ```js {/* ...other components like Controls, MiniMap... */} ``` #### Built-in features * **Tab navigation:** Pressing `Tab` moves focus through all focusable nodes and edges. These elements receive `tabIndex={0}` and, by default, `role="group"` to handle interactivity. * **Select/Deselect:** Press `Enter` or `Space` to select the focused node/edge, and `Escape` to clear the selection. * **Move nodes with arrow keys:** If `nodesDraggable` and `nodesFocusable` are both true (default), you can move the selected node with the arrow keys. You can hold `Shift` to increase movement speed. If you set `disableKeyboardA11y={true}`, arrow-key movement is disabled (but tab-focus and selection still work). * **Automatic panning:** When a node receives focus, React Flow automatically pans the canvas to bring that node into view. This ensures focused nodes are visible to the user. This behavior can be toggled via the [autoPanOnNodeFocus](/api-reference/react-flow#autopanonnodefocus) prop. * **ARIA descriptions:** React Flow automatically adds ARIA descriptions for keyboard guidance. These can be configured by using [ariaLabelConfig](/api-reference/react-flow#arialabelconfig). To enable full keyboard accessibility, ensure both `nodesFocusable` and `edgesFocusable` are set to `true`. If either is false, corresponding elements will not be keyboard-focusable. #### ARIA roles for nodes and edges By default, React Flow uses semantic ARIA roles for interactive elements. You can override a node’s role using the `ariaRole` prop. For example: ```js const nodes = [ { id: '1', data: { label: 'Hello' }, ariaRole: 'button', }, ]; ; ``` By, default, if you don’t set a role, `role="group"` is applied. Customizing `ariaRole` lets you match any relevant ARIA role (e.g. `"listitem"`, `"region"`, etc.), improving semantic information for assistive technology. **Note:** The `ariaRole` is applied to the node wrapper. If a custom node includes interactive elements, avoid setting `ariaRole: 'button'` on the node itself. Instead, apply the appropriate ARIA role directly to the interactive element. ##### DOM attributes To pass custom ARIA attributes or other DOM-level attributes (e.g., `aria-roledescription`, `data-\*`,`tabIndex`, etc.), you can use the `domAttributes` prop. This is available for both nodes and edges: ``` // Example for a node { id: '2', data: { label: 'Accessible Node' }, domAttributes: { 'aria-roledescription': 'collapsible node', tabIndex: 0, 'data-test-id': 'node-2', }, } ``` This gives you full control over accessibility-related attributes while maintaining React Flow’s built-in roles and behaviors. #### Localizing accessibility messages React Flow’s built-in accessibility text (such as keyboard instructions and live updates) can be customized or localized via the [ariaLabelConfig](/api-reference/react-flow#arialabelconfig) prop on``. This prop takes an object mapping message keys to strings or functions. Keys include: | Key | Default Value | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `node.a11yDescription.default` | Press enter or space to select a node. Press delete to remove it and escape to cancel. | | `node.a11yDescription.keyboardDisabled` | Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel. | | `node.a11yDescription.ariaLiveMessage` | `Moved selected node {direction}. New position, x: {x}, y: {y}` | | `edge.a11yDescription.default` | Press enter or space to select an edge. You can then press delete to remove it or escape to cancel. | | `controls.ariaLabel` | Control Panel | | `controls.zoomIn.ariaLabel` | Zoom In | | `controls.zoomOut.ariaLabel` | Zoom Out | | `controls.fitView.ariaLabel` | Fit View | | `controls.interactive.ariaLabel` | Toggle Interactivity | | `minimap.ariaLabel` | Mini Map | | `handle.ariaLabel` | Handle | For example, to provide custom or localized text: ```js const ariaLabels = { 'node.a11yDescription.default': 'Press [Enter] to select this node', 'node.a11yDescription.keyboardDisabled': 'Keyboard navigation is disabled', }; ; ``` This tells React Flow to use your text instead of the defaults. By supplying localized strings via [ariaLabelConfig](/api-reference/react-flow#arialabelconfig), you ensure screen readers announce messages in the user’s language. #### WCAG 2.1 AA React Flow provides features that can help you meet key WCAG 2.1 AA criteria when properly implemented: * **Keyboard:** React Flow supports keyboard operability with `Tab` navigation to nodes and edges, interaction via `Enter`/`Space`, and arrow key movement for nodes. These features help satisfy requirements for keyboard accessibility. * **Screen Reader:** With semantic ARIA roles and labels (e.g. `role="group"`, `aria-label`, and `aria-roledescription`), React Flow enables you to create meaningfully announced graphical nodes/edges. Edge components include a customizable `aria-label` and nodes can be given appropriate `aria-label` text. * **ARIA Live Regions:** Dynamic updates are announced through an `aria-live` region. The `A11yDescriptions` component includes an element with `aria-live="assertive"` that notifies users of node movements, helping you meet requirements for status messages. * **Instructions and Focus Management:** React Flow provides contextual help with clear instructions like "Press enter or space to select a node…". The automatic focus management ensures nodes scroll into view when focused, helping satisfy requirements for input assistance. This guide is helpful for learning about [ARIA best practices](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/). ### Computing Flows import { Callout } from 'nextra/components'; import { RemoteCodeViewer } from 'xy-shared/server'; For this guide we assume that you already know about the [core concepts](/learn/concepts/core-concepts) of React Flow and how to implement [custom nodes](/learn/customization/custom-nodes). Usually with React Flow, developers handle their data outside of React Flow by sending it somewhere else, like on a server or a database. Instead, in this guide we'll show you how to compute data flows directly inside of React Flow. You can use this for updating a node based on connected data, or for building an app that runs entirely inside the browser. #### What are we going to build? By the end of this guide, you will build an interactive flow graph that generates a color out of three separate number input fields (red, green and blue), and determines whether white or black text would be more readable on that background color. #### Creating custom nodes Let's start by creating a custom input node (`NumberInput.js`) and add three instances of it. We will be using a controlled `` and limit it to integer numbers between 0 - 255 inside the `onChange` event handler. ```jsx import { useCallback, useState } from 'react'; import { Handle, Position } from '@xyflow/react'; function NumberInput({ id, data }) { const [number, setNumber] = useState(0); const onChange = useCallback((evt) => { const cappedNumber = Math.round( Math.min(255, Math.max(0, evt.target.value)), ); setNumber(cappedNumber); }, []); return (
{data.label}
); } export default NumberInput; ``` Next, we'll add a new custom node (`ColorPreview.js`) with one target handle for each color channel and a background that displays the resulting color. We can use `mix-blend-mode: 'difference';` to make the text color always readable. Whenever you have multiple handles of the same kind on a single node, don't forget to give each one a separate id! Let's also add edges going from the input nodes to the color node to our `initialEdges` array while we are at it. #### Computing data How do we get the data from the input nodes to the color node? This is a two step process that involves two hooks created for this exact purpose: 1. Store each number input value inside the node's `data` object with help of the [`updateNodeData`](/api-reference/types/react-flow-instance#update-node-data) callback. 2. Find out which nodes are connected by using [`useNodeConnections`](/api-reference/hooks/use-node-connections) and then use [`useNodesData`](/api-reference/hooks/use-nodes-data) for receiving the data from the connected nodes. ##### Step 1: Writing values to the data object First let's add some initial values for the input nodes inside the `data` object in our `initialNodes` array and use them as an initial state for the input nodes. Then we'll grab the function [`updateNodeData`](/api-reference/types/react-flow-instance#update-node-data) from the [`useReactFlow`](/api-reference/hooks/use-react-flow) hook and use it to update the `data` object of the node with a new value whenever the input changes. By default, the data you pass to [`updateNodeData`](/api-reference/types/react-flow-instance#update-node-data) will be merged with the old data object. This makes it easier to do partial updates and saves you in case you forget to add `{...data}`. You can pass `{ replace: true }` as an option to replace the object instead. When dealing with input fields you don't want to use a nodes `data` object as UI state directly. There is a delay in updating the data object and the cursor might jump around erratically and lead to unwanted inputs. ##### Step 2: Getting data from connected nodes We start by determining all connections for each node with the [`useNodeConnections`](/api-reference/hooks/use-node-connections) hook and then fetching the data for the first connected node with [`updateNodeData`](/api-reference/types/react-flow-instance#update-node-data). Note that each handle can have multiple nodes connected to it and you might want to restrict the number of connections to a single handle inside your application. Check out the [connection limit example](/examples/nodes/connection-limit) to see how to do that. And there you go! Try changing the input values and see the color change in real time. ##### Improving the code It might seem awkward to get the connections first, and then the data separately for each handle. For nodes with multiple handles like these, you should consider creating a custom handle component that isolates connection states and node data binding. We can create one inline. ```jsx filename="ColorPreview.js" // {...} function CustomHandle({ id, label, onChange }) { const connections = useNodeConnections({ handleType: 'target', handleId: id, }); const nodeData = useNodesData(connections?.[0].source); useEffect(() => { onChange(nodeData?.data ? nodeData.data.value : 0); }, [nodeData]); return (
); } ``` We can promote color to local state and declare each handle like this: ```jsx filename="ColorPreview.js" // {...} function ColorPreview() { const [color, setColor] = useState({ r: 0, g: 0, b: 0 }); return (
setColor((c) => ({ ...c, r: value }))} /> setColor((c) => ({ ...c, g: value }))} /> setColor((c) => ({ ...c, b: value }))} />
); } export default ColorPreview; ``` #### Getting more complex Now we have a simple example of how to pipe data through React Flow. What if we want to do something more complex, like transforming the data along the way? Or even take different paths? We can do that too! ##### Continuing the flow Let's extend our flow. Start by adding an output `` to the color node and remove the local component state. Because there are no inputs fields on this node, we don't need to keep a local state at all. We can just read and update the node's `data` object directly. Next, we add a new node (`Lightness.js`) that takes in a color object and determines if it is either a light or dark color. We can use the [relative luminance formula](https://en.wikipedia.org/wiki/Relative_luminance#Relative_luminance_and_%22gamma_encoded%22_colorspaces) `luminance = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b` to calculate the perceived brightness of a color (0 being the darkest and 255 being the brightest). We can assume everything >= 128 is a light color. ##### Conditional branching What if we would like to take a different path in our flow based on the perceived lightness? Let's give our lightness node two source handles `light` and `dark` and separate the node `data` object by source handle IDs. This is needed if you have multiple source handles to distinguish between each source handle's data. But what does it mean to "take a different route"? One solution would be to assume that `null` or `undefined` data hooked up to a target handle is considered a "stop". In our case we can write the incoming color into `data.values.light` if it's a light color and into `data.values.dark` if it's a dark color and set the respective other value to `null`. Don't forget to add `flex-direction: column;` and `align-items: end;` to reposition the handle labels. Cool! Now we only need a last node to see if it actually works... We can create a custom debugging node (`Log.js`) that displays the hooked up data, and we're done! #### Summary You have learned how to move data through the flow and transform it along the way. All you need to do is 1. store data inside the node's `data` object with help of [`updateNodeData`](/api-reference/types/react-flow-instance#update-node-data) callback. 2. find out which nodes are connected by using [`useNodeConnections`](/api-reference/hooks/use-node-connections) and then use [`useNodesData`](/api-reference/hooks/use-nodes-data) for receiving the data from the connected nodes. You can implement branching for example by interpreting incoming data that is undefined as a "stop". As a side note, most flow graphs that also have a branching usually separate the triggering of nodes from the actual data hooked up to the nodes. Unreal Engines Blueprints are a good example for this. One last note before you go: you should find a consistent way of structuring all your node data, instead of mixing ideas like we did just now. This means for example, if you start working with splitting data by handle ID you should do it for all nodes, regardless whether they have multiple handles or not. Being able to make assumptions about the structure of your data throughout your flow will make life a lot easier. import { Callout } from 'nextra/components'; import { RemoteCodeViewer } from 'xy-shared/server'; ### Devtools and Debugging This is an ongoing experiment on implementing our own React Flow devtools. While we are working on the actual package, we'd love to hear about your feedback and ideas on [Discord](https://discord.gg/Bqt6xrs) or via mail at [info@xyflow.com](mailto:info@xyflow.com). React Flow can often seem like a magic black box, but in reality you can reveal quite a lot about its internal state if you know where to look. In this guide we will show you three different ways to reveal the internal state of your flow: * A `` component that shows the current position and zoom level of the viewport. * A `` component that reveals the state of each node. * A `` that wraps your flow's `onNodesChange` handler and logs each change as it is dispatched. While we find these tools useful for making sure React Flow is working properly, you might also find them useful for debugging your applications as your flows and their interactions become more complex.
We encourage you to copy any or all of the components from this example into your own projects and modify them to suit your needs: each component works independently! #### Node Inspector The `` component makes use of our [`useNodes`](/api-reference/hooks/use-nodes) hook to access all the nodes in the flow. Typically we discourage using this hook because it will trigger a re-render any time *any* of your nodes change, but that's exactly what makes it so useful for debugging! The `width` and `height` properties are added to each node by React Flow after it has measured the node's dimensions. We pass those dimensions, as well as other information like the node's id and type, to a custom `` component. We make use of the [``](/api-reference/components/viewport-portal) component to let us render the inspector into React Flow's viewport. That means it's content will be positioned and transformed along with the rest of the flow as the user pans and zooms. #### Change Logger Any change to your nodes and edges that originates from React Flow itself is communicated to you through the `onNodesChange` and `onEdgesChange` callbacks. If you are working with a controlled flow (that means you're managing the nodes and edges yourself), you need to apply those changes to your state in order to keep everything in sync. The `` component wraps your user-provided `onNodesChange` handler with a custom function that intercepts and logs each change as it is dispatched. We can do this by using the [`useStore`](/api-reference/hooks/use-store) and [`useStoreApi`](/api-reference/hooks/use-store-api) hooks to access the store and and then update React Flow's internal state accordingly. These two hooks give you powerful access to React Flow's internal state and methods. Beyond debugging, using the `` can be a great way to learn more about how React Flow works and get you thinking about the different functionality you can build on top of each change. You can find documentation on the [`NodeChange`](/api-reference/types/node-change) and [`EdgeChange`](/api-reference/types/edge-change) types in the API reference. #### Viewport Logger The `` is the simplest example of what state you can pull out of React Flow's store if you know what to look for. The state of the viewport is stored internally under the `transform` key (a name we inherited from [d3-zoom](https://d3js.org/d3-zoom#zoomTransform)). This component extracts the `x`, `y`, and `zoom` components of the transform and renders them into a [``](/api-reference/components/panel) component. #### Let us know what you think As mentioned above, if you have any feedback or ideas on how to improve the devtools, please let us know on [Discord](https://discord.gg/Bqt6xrs) or via mail at [info@xyflow.com](mailto:info@xyflow.com). If you build your own devtools using these ideas, we'd love to hear about it! import { RemoteCodeViewer } from 'xy-shared/server'; ### Hooks and Providers React Flow provides several [hooks](/api-reference/hooks) and a context provider for you to enhance the functionality of your flow. These tools help you to manage state, access internal methods, and create custom components more effectively. #### ReactFlowProvider The ReactFlowProvider is a context provider that allows you to access the internal state of the flow, such as nodes, edges, and viewport, from anywhere in your component tree even outside the [`ReactFlow`](/api-reference/react-flow) component. It is typically used at the top level of your application. There are several cases where you might need to use the [`ReactFlowProvider`](/api-reference/react-flow-provider) component: * Many of the [hooks](/api-reference/hooks) we provide rely on this component to work. * You want to access the internal state of the flow outside of the `ReactFlow` component. * You are working with multiple flows on a page. * You are using a client-side router. #### useReactFlow The [`useReactFlow`](/api-reference/hooks/use-react-flow) hook provides access to the [`ReactFlowInstance`](/api-reference/types/react-flow-instance) and its methods. It allows you to manipulate nodes, edges, and the viewport programmatically. This example illustrates how to use the `useReactFlow` hook. ### Performance When dealing with a large number of nodes or complex components, managing performance can be challenging. Here are a few effective strategies to optimize the performance of React Flow. #### Use memoization One of the main reasons for performance issues in React Flow is unnecessary re-renders. Since node movements trigger frequent state updates, this can lead to performance bottlenecks, especially in larger diagrams. ##### Memoize components Components provided as props to the `` component, including custom node and edge components, should either be memoized using `React.memo` or declared outside the parent component. This ensures that React does not create a new reference for the component on every render, which would otherwise trigger unnecessary re-renders. ```tsx const NodeComponent = memo(() => { return
{data.label}
; }); ``` ##### Memoize functions Similarly, functions passed as props to `` should be memoized using `useCallback`. This prevents React from creating a new function reference on every render, which could also trigger unnecessary re-renders. Additionally, arrays and objects like `defaultEdgeOptions` or `snapGrid` should be memoized using `useMemo` to prevent unnecessary re-renders. ```tsx import React, { useCallback } from 'react'; const MyDiagram = () => { const onNodeClick = useCallback((event, node) => { console.log('Node clicked:', node); }, []); return ; }; export default MyDiagram; ``` #### Avoid accessing nodes in components One of the most common performance pitfalls in React Flow is directly accessing the `nodes` or `edges` in the components or the viewport. These objects change frequently during operations like dragging, panning, or zooming, which can cause unnecessary re-renders of components that depend on them. For example, if you fetch the entire `nodes` array from the store and filter it to display selected node IDs, this approach can lead to performance degradation. Every update to the `nodes` array triggers a re-render of all dependent components, even if the change is unrelated to the selected nodes. ##### Inefficient example ```tsx const SelectedNodeIds = () => { // ❌ This will cause unnecessary re-renders! const nodes = useStore((state) => state.nodes); const selectedNodeIds = nodes.filter((node) => node.selected).map((node) => node.id); return (
{selectedNodeIds.map((id) => (
{id}
))}
); }; ``` In this example, every update to the `nodes` array causes the `SelectedNodeIds` component to re-render, even if the selection hasn’t changed. ##### Optimized solution To avoid unnecessary re-renders, store the selected nodes in a separate field in your state (using Zustand, Redux, or any other state management solution). This ensures that the component only re-renders when the selection changes. ```tsx const SelectedNodeIds = () => { const selectedNodeIds = useStore((state) => state.selectedNodeIds); return (
{selectedNodeIds.map((id) => (
{id}
))}
); }; ``` By decoupling the selected nodes from the `nodes` array, you prevent unnecessary updates and improve performance. For more information, view our [State Management guide](/learn/advanced-use/state-management). #### Collapse large node trees If your node tree is deeply nested, rendering all nodes at once can be inefficient. Instead, show only a limited number of nodes and allow users to expand them as needed. You can do this by modifying the node’s `hidden` property dynamically to toggle visibility. ```tsx const handleNodeClick = (targetNode) => { if (targetNode.data.children) { setNodes((prevNodes) => prevNodes.map((node) => targetNode.data.children.includes(node.id) ? { ...node, hidden: !node.hidden } : node, ), ); } }; ``` By hiding nodes initially and rendering them only when expanded, we optimize performance while maintaining usability. #### Simplify node and edge styles If you've optimized performance in every other way, and you are still finding performance issues with large numbers of nodes, complex CSS styles, particularly those involving animations, shadows, or gradients, can significantly impact performance. Consider reducing complexity on your node styles in these cases. #### Additional resources Here are a few helpful resources on performance in React Flow that you can check out: * [Guide to Optimize React Flow Project Performance](https://www.synergycodes.com/blog/guide-to-optimize-react-flow-project-performance) * [Tuning Edge Animations ReactFlow Optimal Performance](https://liambx.com/blog/tuning-edge-animations-reactflow-optimal-performance) * [5 Ways to Optimize React Flow in 10 minutes](https://www.youtube.com/watch?v=8M2qZ69iM20) ### Server Side Rendering ### Server side rendering, server side generation import { Callout } from 'nextra/components'; Server side rendering is supported since React Flow 12 This is an advanced use case and assumes you are already familiar with React Flow. If you're new to React Flow, check out our [getting started guide](/learn/getting-started/installation-and-requirements). In this guide you will learn how to configure React Flow to render a flow on the server, which will allow you to * Display static HTML diagrams in documentation * Render React Flow diagrams in non-js environments * Dynamically generate open graph images that appear as embeds when sharing a link to your flow (If you want to download an image of your flow, there's an easier way to do that on the client-side in our [download image example](/examples/misc/download-image).) ##### Node dimensions You need to configure a few things to make React Flow work on the server, the most important being the node dimensions. React Flow only renders nodes if they have a width and height. Usually you pass nodes without a specific `width` and `height`, they are then measured and the dimensions get written to `measured.width` and `measured.height`. Since we can't measure the dimensions on the server, we need to pass them explicitly. This can be done with the `width` and `height` or the `initialWidth` and `initialHeight` node properties. ```js const nodes = [ { id: '1', type: 'default', position: { x: 0, y: 0 }, data: { label: 'Node 1' }, width: 100, height: 50, }, ]; ``` React Flow now knows the dimensions of the node and can render it on the server. The `width` and `height` properties are used as an inline style for the node. If you expect nodes to have different dimensions on the client or if the dimensions should by dynamic based on the content, you can use the `initialWidth` and `initialHeight` properties. They are only used for the first render (on the server or on the client) as long as the nodes are not measured and `measured.width` and `measured.height` are not set. There are two ways to specify node dimensions for server side rendering:
1. `width` and `height` for static dimensions that are known in advance and don't change.
2. `initialWidth` and `initialHeight` for dynamic dimensions that are not known in advance or change.
##### Handle positions You probably also want to render the edges on the server. On the client, React Flow checks the positions of the handles and stores that information to draw the edges. Since we can't measure the handle positions on the server, we need to pass this information, too. This can be done with the `handles` property of a node. ```js const nodes: Node[] = [ { id: '1', type: 'default', position: { x: 0, y: 0 }, data: { label: 'Node 1' }, width: 100, height: 50, handles: [ { type: 'target', position: Position.Top, x: 100 / 2, y: 0, }, { type: 'source', position: Position.Bottom, x: 100 / 2, y: 50, }, ], }, ]; ``` With this additional information, React Flow knows enough about the handles to render the edges on the server. If you are fine with just rendering the nodes, you can skip this step. ##### Using `fitView` on the server If you know the dimensions of the React Flow container itself, you can even use `fitView` on the server. For this, you need to pass the `width` and `height` of the container to the `ReactFlow` component. ```js ``` This will calculate the viewport and set the `transform` on the server in order to include all nodes in the viewport. ##### Usage with the `` If you are using the `ReactFlowProvider`, you can pass `initialNodes`, `initialEdges` and optional wrapper dimensions (`initialWidth` and `initialHeight`) and `fitView` to the provider. ```js ``` The `initial-` prefix means that these values are only used for the first render. After that, the provider will use the `nodes` and `edges` from the context. ##### Creating static HTML If you want to create static HTML, you can use the `renderToStaticMarkup` function from React. This will render the React Flow component to a string of HTML. You can then use this string to create a static HTML file or send it as a response to an HTTP request. ```js import React from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { ReactFlow, Background } from '@xyflow/react'; function toHTML({ nodes, edges, width, height }) { const html = renderToStaticMarkup( React.createElement( ReactFlow, { nodes, edges, width, height, minZoom: 0.2, fitView: true, }, React.createElement(Background, null), ), ); return html; } ``` ### Using a State Management Library import { Callout } from 'nextra/components'; import { RemoteCodeViewer } from 'xy-shared/server'; For this guide we assume that you already know about the [core concepts](/learn/concepts/core-concepts) of React Flow and how to implement [custom nodes](/learn/customization/custom-nodes). You should also be familiar with the concepts of state management libraries and how to use them. In this guide, we explain how to use React Flow with the state management library [Zustand](https://github.com/pmndrs/zustand). We will build a small app where each node features a color chooser that updates its background color. We chose Zustand for this guide because React Flow already uses it internally, but you can easily use other state management libraries such as [Redux](https://redux.js.org/), [Recoil](https://recoiljs.org/) or [Jotai](https://jotai.org/) As demonstrated in previous guides and examples, React Flow can easily be used with a local component state to manage nodes and edges in your diagram. However, as your application grows and you need to update the state from within individual nodes, managing this state can become more complex. Instead of passing functions through the node's data field, you can use a [React context](https://reactjs.org/docs/context.html) or integrate a state management library like Zustand, as outlined in this guide. #### Install Zustand As mentioned above we are using Zustand in this example. Zustand is a bit like Redux: you have a central store with actions to alter your state and hooks to access your state. You can install Zustand via: ```bash copy npm2yarn npm install --save zustand ``` #### Create a store Zustand lets you create a hook for accessing the values and functions of your store. We put the `nodes` and `edges` and the `onNodesChange`, `onEdgesChange`, `onConnect`, `setNodes` and `setEdges` functions in the store to get the basic interactivity for our graph: That's the basic setup. We now have a store with nodes and edges that can handle the changes (dragging, selecting or removing a node or edge) triggered by React Flow. When you take a look at the `App.tsx` file, you can see that it's kept nice and clean. All the data and actions are now part of the store and can be accessed with the `useStore` hook. #### Implement a color change action We add a new `updateNodeColor` action to update the `data.color` field of a specific node. For this we pass the node id and the new color to the action, iterate over the nodes and update the matching one with the new color: ```ts updateNodeColor: (nodeId: string, color: string) => { set({ nodes: get().nodes.map((node) => { if (node.id === nodeId) { // it's important to create a new object here, to inform React Flow about the changes return { ...node, data: { ...node.data, color } }; } return node; }), }); }; ``` This new action can now be used in a React component like this: ```tsx const updateNodeColor = useStore((s) => s.updateNodeColor); ... ); } ``` If we try to use this edge now, we'll see that the button is rendered in the centre of the flow (it might be hidden behind "Node A"). Because of the edge label portal, we'll need to do some extra work to position the button ourselves. A screen shot of a simple flow. The edge label renderer is highlighted in
the DOM inspector and the button is rendered in the centre of the flow. Fortunately, the path utils we've already seen can help us with this! Along with the SVG path to render, these functions also return the `x` and `y` coordinates of the path's midpoint. We can then use these coordinates to translate our custom edge label's into the right position! ```jsx export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY }) { const { deleteElements } = useReactFlow(); const [edgePath, labelX, labelY] = getStraightPath({ ... }); return ( ... Node Actions Reset Delete
{String(data.value).padStart(3, ' ')}
); } ``` This isn't a tutorial for basic React Flow concepts like flows and custom nodes so we're **skipping over some of the basics**. If you're new to React Flow and want to learn how to add custom nodes and edges to a flow, check out the [guide on custom nodes](/learn/customization/custom-nodes). In the snippet above we've highlighted the imports and components that come from shadcn/ui and React Flow Components. In just a few lines of code we already have quite a capable node: Our `` component... * Has a header with a title and functional dropdown menu. * Contains some simple controls to increment and decrement a value. * Has a labelled handle to connect it to other nodes. Next we'll create a second node that will compute the sum of two input values. We don't need to add any additional components for this node, so go ahead and create a new file `src/components/nodes/sum-node.tsx` and paste in the following: ```tsx filename="src/components/nodes/sum-node.tsx" import { type Node, type NodeProps, Position, useReactFlow, useStore, } from '@xyflow/react'; import { useEffect } from 'react'; import { BaseNode, BaseNodeHeader, BaseNodeHeaderTitle } from '../base-node'; import { LabeledHandle } from '../labeled-handle'; export type SumNode = Node<{ value: number; }>; export function SumNode({ id }: NodeProps) { const { updateNodeData, getHandleConnections } = useReactFlow(); const { x, y } = useStore((state) => ({ x: getHandleValue( getHandleConnections({ nodeId: id, id: 'x', type: 'target' }), state.nodeLookup, ), y: getHandleValue( getHandleConnections({ nodeId: id, id: 'y', type: 'target' }), state.nodeLookup, ), })); useEffect(() => { updateNodeData(id, { value: x + y }); }, [x, y]); return ( Sum
); } function getHandleValue( connections: Array<{ source: string }>, lookup: Map>, ) { return connections.reduce((acc, { source }) => { const node = lookup.get(source)!; const value = node.data.value; return typeof value === 'number' ? acc + value : acc; }, 0); } ``` React Flow UI doesn't just provide components for building nodes. We also provide pre-built edges and other UI elements you can drop into your flows for quick building. To better visualize data in our calculator flow, let's pull in the `data-edge` component. This edge renders a field from the source node's data object as a label on the edge itself. Add the `data-edge` component to your project: ```bash copy npm2yarn npx shadcn@latest add https://ui.reactflow.dev/data-edge ``` The `` component works by looking up a field from its source node's `data` object. We've been storing the value of each node in our calculator field in a `"value"` property so we'll update our `edgeType` object to include the new `data-edge` and we'll update the `onConnect` handler to create a new edge of this type, making sure to set the edge's `data` object correctly: ```tsx filename="src/App.tsx" import '@xyflow/react/dist/style.css'; import { ReactFlow, OnConnect, Position, useNodesState, useEdgesState, addEdge, Edge, Node, } from '@xyflow/react'; import { NumNode } from '@/components/nodes/num-node'; import { SumNode } from '@/components/nodes/sum-node'; import { DataEdge } from '@/components/data-edge'; const nodeTypes = { num: NumNode, sum: SumNode, }; const initialNodes: Node[] = [ { id: 'a', type: 'num', data: { value: 0 }, position: { x: 0, y: 0 } }, { id: 'b', type: 'num', data: { value: 0 }, position: { x: 0, y: 200 } }, { id: 'c', type: 'sum', data: { value: 0 }, position: { x: 300, y: 100 } }, { id: 'd', type: 'num', data: { value: 0 }, position: { x: 0, y: 400 } }, { id: 'e', type: 'sum', data: { value: 0 }, position: { x: 600, y: 400 } }, ]; const edgeTypes = { data: DataEdge, }; const initialEdges: Edge[] = [ { id: 'a->c', type: 'data', data: { key: 'value' }, source: 'a', target: 'c', targetHandle: 'x', }, { id: 'b->c', type: 'data', data: { key: 'value' }, source: 'b', target: 'c', targetHandle: 'y', }, { id: 'c->e', type: 'data', data: { key: 'value' }, source: 'c', target: 'e', targetHandle: 'x', }, { id: 'd->e', type: 'data', data: { key: 'value' }, source: 'd', target: 'e', targetHandle: 'y', }, ]; function App() { const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect: OnConnect = useCallback( (params) => { setEdges((edges) => addEdge({ type: 'data', data: { key: 'value' }, ...params }, edges), ); }, [setEdges], ); return (
); } export default App; ``` Putting everything together we end up with quite a capable little calculator! You could continue to improve this flow by adding nodes to perform other operations or to take user input using additional components from the [shadcn/ui registry](https://ui.shadcn.com/docs/components/slider). In fact, keep your eyes peeled soon for a follow-up to this guide where we'll show a complete application built using React Flow Components . #### Wrapping up In just a short amount of time we've managed to build out a fairly complete flow using the components and building blocks provided by shadcn React Flow Components. We've learned: * How editing the [``](/ui/components/base-node) component will affect other nodes pulled from the React Flow UI registry. * How to use building blocks like the [``](/ui/components/base-node) and [``](/ui/components/labeled-handle) components to build our own custom nodes without starting from scratch. * That React Flow UI also provides custom edges like the [``](/ui/components/data-edge) to drop into our applications. And thanks to the power of Tailwind, tweaking the visual style of these components is as simple as editing `tailwind.config.js` and editing the variables in your CSS file. That's all for now! You can see all the components we currently have available over on the [UI docs page](/ui). The React Flow UI project is still in its infancy: if you have any suggestions or requests for new components we'd love to hear about them. Or perhaps you're already starting to build something with shadcn and React Flow UI. Either way make sure you let us know on our [Discord server](https://discord.com/invite/RVmnytFmGW) or on [Twitter](https://twitter.com/xyflowdev)! ### Build a Mind Map App with React Flow import { Emoji } from '@xyflow/xy-ui'; import { RemoteCodeViewer } from 'xy-shared/server'; In this tutorial, you will learn to create a simple mind map tool with React Flow that can be used for brainstorming, organizing an idea, or mapping your thoughts in a visual way. To build this app, we'll be using state management, custom nodes and edges, and more. #### It's Demo Time! Before we get our hands dirty, I want to show you the mind-mapping tool we'll have by the end of this tutorial: If you'd like to live dangerously and dive right into the code, you can find the source code on [Github](https://github.com/xyflow/react-flow-mindmap-app). #### Getting started To do this tutorial you will need some knowledge of [React](https://reactjs.org/docs/getting-started.html) and [React Flow](/learn/concepts/terms-and-definitions) (hi, that's us! it's an open source library for building node-based UIs like workflow tools, ETL pipelines, and [more](/showcase/).) We'll be using [Vite](https://vitejs.dev/) to develop our app, but you can also use [Create React App](https://create-react-app.dev/) or any other tool you like. To scaffold a new React app with Vite you need to do: ```bash npm2yarn npm create vite@latest reactflow-mind-map -- --template react ``` if you would like to use Typescript: ```bash npm2yarn npm create vite@latest reactflow-mind-map -- --template react-ts ``` After the initial setup, you need to install some packages: ```bash npm2yarn npm install reactflow zustand classcat nanoid ``` We are using [Zustand](https://github.com/pmndrs/zustand) for managing the state of our application. It's a bit like Redux but way smaller and there's less boilerplate code to write. React Flow also uses Zustand, so the installation comes with no additional cost. (For this tutorial we are using Typescript but you can also use plain Javascript.) To keep it simple we are putting all of our code in the `src/App` folder. For this you need to create the `src/App` folder and add an index file with the following content: ###### src/App/index.tsx ```tsx import { ReactFlow, Controls, Panel } from '@xyflow/react'; // we have to import the React Flow styles for it to work import '@xyflow/react/dist/style.css'; function Flow() { return ( React Flow Mind Map ); } export default Flow; ``` This will be our main component for rendering the mind map. There are no nodes or edges yet, but we added the React Flow [`Controls`](/api-reference/components/controls) component and a [`Panel`](/api-reference/components/panel) to display the title of our app. To be able to use React Flow hooks, we need to wrap the application with the [`ReactFlowProvider`](/api-reference/react-flow-provider) component in our main.tsx (entry file for vite). We are also importing the newly created `App/index.tsx` and render it inside the `ReactFlowProvider.` Your main file should look like this: ###### src/main.tsx ```tsx import React from 'react'; import ReactDOM from 'react-dom/client'; import { ReactFlowProvider } from '@xyflow/react'; import App from './App'; import './index.css'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( , ); ``` The parent container of the React Flow component needs a width and a height to work properly. Our app is a fullscreen app, so we add these rules to the `index.css` file: ###### src/index.css ```css body { margin: 0; } html, body, #root { height: 100%; } ``` We are adding all styles of our app to the `index.css` file (you could also use [Tailwind](/examples/styling/tailwind)). Now you can start the development server with `npm run dev` and you should see the following: #### A store for nodes and edges As mentioned above, we are using Zustand for state management. For this, we create a new file in our `src/App` folder called `store.ts`: ###### src/App/store.ts ```ts import { Edge, EdgeChange, Node, NodeChange, OnNodesChange, OnEdgesChange, applyNodeChanges, applyEdgeChanges, } from '@xyflow/react'; import { createWithEqualityFn } from 'zustand/traditional'; export type RFState = { nodes: Node[]; edges: Edge[]; onNodesChange: OnNodesChange; onEdgesChange: OnEdgesChange; }; const useStore = createWithEqualityFn((set, get) => ({ nodes: [ { id: 'root', type: 'mindmap', data: { label: 'React Flow Mind Map' }, position: { x: 0, y: 0 }, }, ], edges: [], onNodesChange: (changes: NodeChange[]) => { set({ nodes: applyNodeChanges(changes, get().nodes), }); }, onEdgesChange: (changes: EdgeChange[]) => { set({ edges: applyEdgeChanges(changes, get().edges), }); }, })); export default useStore; ``` It seems like a lot of code, but it's mostly types The store keeps track of the nodes and edges and handles the change events. When a user drags a node, React Flow fires a change event, the store then applies the changes and the updated nodes get rendered. (You can read more about this in our [state management library guide](/api-reference/hooks/use-store).) As you can see we start with one initial node placed at `{ x: 0, y: 0 }` of type 'mindmap'. To connect the store with our app, we use the `useStore` hook: ###### src/App/index.tsx ```tsx import { ReactFlow, Controls, Panel, NodeOrigin } from '@xyflow/react'; import { shallow } from 'zustand/shallow'; import useStore, { RFState } from './store'; // we have to import the React Flow styles for it to work import '@xyflow/react/dist/style.css'; const selector = (state: RFState) => ({ nodes: state.nodes, edges: state.edges, onNodesChange: state.onNodesChange, onEdgesChange: state.onEdgesChange, }); // this places the node origin in the center of a node const nodeOrigin: NodeOrigin = [0.5, 0.5]; function Flow() { // whenever you use multiple values, you should use shallow to make sure the component only re-renders when one of the values changes const { nodes, edges, onNodesChange, onEdgesChange } = useStore(selector, shallow); return ( React Flow Mind Map ); } export default Flow; ``` We access the nodes, edges and change handlers from the store and pass them to the React Flow component. We also use the `fitView` prop to make sure that the initial node is centered in the view and set the node origin to `[0.5, 0.5]` to set the origin to the center of a node. After this, your app should look like this: You can move the node around and zoom in and out, we are getting somewhere Now let's add some more functionality. #### Custom nodes and edges We want to use a custom type called 'mindmap' for our nodes. We need to add a new component for this. Let's create a new folder called `MindMapNode` with an index file under `src/App` with the following content: ###### src/App/MindMapNode/index.tsx ```tsx import { Handle, NodeProps, Position } from '@xyflow/react'; export type NodeData = { label: string; }; function MindMapNode({ id, data }: NodeProps) { return ( <> ); } export default MindMapNode; ``` We are using an input for displaying and editing the labels of our mind map nodes, and two handles for connecting them. This is necessary for React Flow to work; the handles are used as the start and end position of the edges. We also add some CSS to the `index.css` file to make the nodes look a bit prettier: ###### src/index.css ```css .react-flow__node-mindmap { background: white; border-radius: 2px; border: 1px solid transparent; padding: 2px 5px; font-weight: 700; } ``` (For more on this, you can read the [guide to custom nodes](/learn/customization/custom-nodes) in our docs.) Let's do the same for the custom edge. Create a new folder called `MindMapEdge` with an index file under `src/App`: ###### src/App/MindMapEdge/index.tsx ```tsx import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react'; function MindMapEdge(props: EdgeProps) { const { sourceX, sourceY, targetX, targetY } = props; const [edgePath] = getStraightPath({ sourceX, sourceY, targetX, targetY, }); return ; } export default MindMapEdge; ``` I will get into more detail about the custom nodes and edges in the next section. For now it's important that we can use the new types in our app, by adding the following to our `Flow` component: ```tsx import MindMapNode from './MindMapNode'; import MindMapEdge from './MindMapEdge'; const nodeTypes = { mindmap: MindMapNode, }; const edgeTypes = { mindmap: MindMapEdge, }; ``` and then pass the newly created types to the React Flow component. Nice! We can already change the labels of our nodes by clicking in the input field and typing something. #### New nodes We want to make it super quick for a user to create a new node. The user should be able to add a new node by clicking on a node and drag to the position where a new node should be placed. This functionality is not built into React Flow, but we can implement it by using the [`onConnectStart` and `onConnectEnd`](/api-reference/react-flow#onconnectstart) handlers. We are using the start handler to remember the node that was clicked and the end handler to create the new node: ###### Add to src/App/index.tsx ```tsx const connectingNodeId = useRef(null); const onConnectStart: OnConnectStart = useCallback((_, { nodeId }) => { connectingNodeId.current = nodeId; }, []); const onConnectEnd: OnConnectEnd = useCallback((event) => { // we only want to create a new node if the connection ends on the pane const targetIsPane = (event.target as Element).classList.contains('react-flow__pane'); if (targetIsPane && connectingNodeId.current) { console.log(`add new node with parent node ${connectingNodeId.current}`); } }, []); ``` Since our nodes are managed by the store, we create an action to add a new node and its edge. This is how our `addChildNode` action looks: ###### New action in src/store.ts ```ts addChildNode: (parentNode: Node, position: XYPosition) => { const newNode = { id: nanoid(), type: 'mindmap', data: { label: 'New Node' }, position, parentNode: parentNode.id, }; const newEdge = { id: nanoid(), source: parentNode.id, target: newNode.id, }; set({ nodes: [...get().nodes, newNode], edges: [...get().edges, newEdge], }); }; ``` We are using the passed node as a parent. Normally this feature is used to implement [grouping](/examples/nodes/dynamic-grouping) or [sub flows](/examples/grouping/sub-flows). Here we are using it to move all child nodes when their parent is moved. It enables us to clean up and re-order the mind map so that we don't have to move all child nodes manually. Let's use the new action in our `onConnectEnd` handler: ###### Adjustments in src/App/index.tsx ```tsx const store = useStoreApi(); const onConnectEnd: OnConnectEnd = useCallback( (event) => { const { nodeLookup } = store.getState(); const targetIsPane = (event.target as Element).classList.contains('react-flow__pane'); if (targetIsPane && connectingNodeId.current) { const parentNode = nodeLookup.get(connectingNodeId.current); const childNodePosition = getChildNodePosition(event, parentNode); if (parentNode && childNodePosition) { addChildNode(parentNode, childNodePosition); } } }, [getChildNodePosition], ); ``` First we are getting the `nodeLookup` from the React Flow store via `store.getState()`. `nodeLookup` is a map that contains all nodes and their current state. We need it to get the position and dimensions of the clicked node. Then we check if the target of the onConnectEnd event is the React Flow pane. If it is, we want to add a new node. For this we are using our `addChildNode` and the newly created `getChildNodePosition` helper function. ###### Helper function in src/App/index.tsx ```tsx const getChildNodePosition = (event: MouseEvent, parentNode?: Node) => { const { domNode } = store.getState(); if ( !domNode || // we need to check if these properties exist, because when a node is not initialized yet, // it doesn't have a positionAbsolute nor a width or height !parentNode?.computed?.positionAbsolute || !parentNode?.computed?.width || !parentNode?.computed?.height ) { return; } const panePosition = screenToFlowPosition({ x: event.clientX, y: event.clientY, }); // we are calculating with positionAbsolute here because child nodes are positioned relative to their parent return { x: panePosition.x - parentNode.computed?.positionAbsolute.x + parentNode.computed?.width / 2, y: panePosition.y - parentNode.computed?.positionAbsolute.y + parentNode.computed?.height / 2, }; }; ``` This function returns the position of the new node we want to add to our store. We are using the [`project` function](/api-reference/types/react-flow-instance#project) to convert screen coordinates into React Flow coordinates. As mentioned earlier, child nodes are positioned relative to their parents. That's why we need to subtract the parent position from the child node position. That was a lot to take in, let's see it in action: To test the new functionality you can start a connection from a handle and then end it on the pane. You should see a new node being added to the mind map. #### Keep data in sync We can already update the labels but we are not updating the nodes data object. This is important to keep our app in sync and if we want to save our nodes on the server for example. To achieve this we add a new action called `updateNodeLabel` to the store. This action takes a node id and a label. The implementation is pretty straight forward: we iterate over the existing nodes and update the matching one with the passed label: ###### src/store.ts ```ts updateNodeLabel: (nodeId: string, label: string) => { set({ nodes: get().nodes.map((node) => { if (node.id === nodeId) { // it's important to create a new object here, to inform React Flow about the changes node.data = { ...node.data, label }; } return node; }), }); }, ``` Let's use the new action in our `MindmapNode` component: ###### src/App/MindmapNode/index.tsx ```tsx import { Handle, NodeProps, Position } from '@xyflow/react'; import useStore from '../store'; export type NodeData = { label: string; }; function MindMapNode({ id, data }: NodeProps) { const updateNodeLabel = useStore((state) => state.updateNodeLabel); return ( <> updateNodeLabel(id, evt.target.value)} className="input" /> ); } export default MindMapNode; ``` That was quick! The input fields of the custom nodes now display the current label of the nodes. You could take your nodes data, save it on the server and then load it again. #### Simpler UX and nicer styling Functionality-wise we are finished with our mind map app! We can add new nodes, update their labels and move them around. But the UX and styling could use some improvements. Let's make it easier to drag the nodes and to create new nodes! ##### 1. A node as handle Let's use the whole node as a handle, rather than displaying the default handles. This makes it easier to create nodes, because the area where you can start a new connection gets bigger. We need to style the source handle to be the size of the node and hide the target handle visually. React Flow still needs it to connect the nodes but we don't need to display it since we are creating new nodes by dropping an edge on the pane. We use plain old CSS to hide the target handle and position it in the center of the node: ###### src/index.css ```css .react-flow__handle.target { top: 50%; pointer-events: none; opacity: 0; } ``` In order to make the whole node a handle, we also update the style of the source: ###### src/index.css ```css .react-flow__handle.source { top: 0; left: 0; transform: none; background: #f6ad55; height: 100%; width: 100%; border-radius: 2px; border: none; } ``` This works but we can't move the nodes anymore because the source handle is now the whole node and covers the input field. We fix that by using the [`dragHandle` node option](/api-reference/types/node#drag-handle). It allows us to specify a selector for a DOM element that should be used as a drag handle. For this we adjust the custom node a bit: ###### src/App/MindmapNode/index.tsx ```tsx import { Handle, NodeProps, Position } from '@xyflow/react'; import useStore from '../store'; export type NodeData = { label: string; }; function MindMapNode({ id, data }: NodeProps) { const updateNodeLabel = useStore((state) => state.updateNodeLabel); return ( <>
{/* icon taken from grommet https://icons.grommet.io */}
updateNodeLabel(id, evt.target.value)} className="input" />
); } export default MindMapNode; ``` We add a wrapper div with the class name `inputWrapper` and a div with the class name `dragHandle` that acts as the drag handle (surprise!). Now we can style the new elements: ###### src/index.css ```css .inputWrapper { display: flex; height: 20px; z-index: 1; position: relative; } .dragHandle { background: transparent; width: 14px; height: 100%; margin-right: 4px; display: flex; align-items: center; } .input { border: none; padding: 0 2px; border-radius: 1px; font-weight: 700; background: transparent; height: 100%; color: #222; } ``` ##### 2. Activate input on focus We are almost there but we need to adjust some more details. We want to start our new connection from the center of the node. For this we set the pointer events of the input to "none" and check if the user releases the button on top of the node. Only then we want to activate the input field. We can use our `onConnectEnd` function to achieve this: ###### src/App/index.tsx ```tsx const onConnectEnd: OnConnectEnd = useCallback( (event) => { const { nodeLookup } = store.getState(); const targetIsPane = (event.target as Element).classList.contains('react-flow__pane'); const node = (event.target as Element).closest('.react-flow__node'); if (node) { node.querySelector('input')?.focus({ preventScroll: true }); } else if (targetIsPane && connectingNodeId.current) { const parentNode = nodeLookup.get(connectingNodeId.current); const childNodePosition = getChildNodePosition(event, parentNode); if (parentNode && childNodePosition) { addChildNode(parentNode, childNodePosition); } } }, [getChildNodePosition], ); ``` As you see we are focusing the input field if the user releases the mouse button on top of a node. We can now add some styling so that the input field is activated (pointerEvents: all) only when it's focused: ```css /* we want the connection line to be below the node */ .react-flow .react-flow__connectionline { z-index: 0; } /* pointer-events: none so that the click for the connection goes through */ .inputWrapper { display: flex; height: 20px; position: relative; z-index: 1; pointer-events: none; } /* pointer-events: all so that we can use the drag handle (here the user cant start a new connection) */ .dragHandle { background: transparent; width: 14px; height: 100%; margin-right: 4px; display: flex; align-items: center; pointer-events: all; } /* pointer-events: none by default */ .input { border: none; padding: 0 2px; border-radius: 1px; font-weight: 700; background: transparent; height: 100%; color: #222; pointer-events: none; } /* pointer-events: all when it's focused so that we can type in it */ .input:focus { border: none; outline: none; background: rgba(255, 255, 255, 0.25); pointer-events: all; } ``` ##### 3. Dynamic width and auto focus Almost done! We want to have a dynamic width for the nodes based on the length of the text. To keep it simple we do a calculation based on the length of text for this: ###### Added effect in src/app/MindMapNode.tsx ```jsx useLayoutEffect(() => { if (inputRef.current) { inputRef.current.style.width = `${data.label.length * 8}px`; } }, [data.label.length]); ``` We also want to focus / activate a node right after it gets created: ###### Added effect in src/app/MindMapNode.tsx ```jsx useEffect(() => { setTimeout(() => { if (inputRef.current) { inputRef.current.focus({ preventScroll: true }); } }, 1); }, []); ``` Now when you adjust a node label, the width of the node will adjust accordingly. You can also create a new node and it will be focused right away. ##### 4. Centered edges and styling details You may have noticed that the edges are not centered. We created a custom edge at the beginning for this, and now we can adjust it a bit so that the edge starts in the center of the node and not at the top of the handle (the default behavior): ###### src/App/MindMapEdge.tsx ```tsx import { BaseEdge, EdgeProps, getStraightPath } from '@xyflow/react'; function MindMapEdge(props: EdgeProps) { const { sourceX, sourceY, targetX, targetY } = props; const [edgePath] = getStraightPath({ sourceX, sourceY: sourceY + 20, targetX, targetY, }); return ; } export default MindMapEdge; ``` We are passing all props to the [`getStraightPath`](/api-reference/utils/get-straight-path) helper function but adjust the sourceY so that it is in the center of the node. More over we want the title to be a bit more subtle and choose a color for our background. We can do this by adjusting the color of the panel (we added the class name `"header"`) and the background color of the body element: ```css body { margin: 0; background-color: #f8f8f8; height: 100%; } .header { color: #cdcdcd; } ``` Nicely done! You can find the final code here: #### Final thoughts What a trip! We started with an empty pane and ended with a fully functional mind map app. If you want to move on you could work on some of the following features: * Add new nodes by clicking on the pane * Save and restore button to store current state to local storage * Export and import UI * Collaborative editing I hope you enjoyed this tutorial and learned something new! If you have any questions or feedback, feel free to reach out to me on [Twitter](https://twitter.com/moklick) or join our [Discord server](https://discord.com/invite/RVmnytFmGW). React Flow is an independent company financed by its users. If you want to support us you can [sponsor us on Github](https://github.com/sponsors/xyflow) or [subscribe to one of our Pro plans](/pro/). ### Integrating React Flow and the Web Audio API import { Emoji } from '@xyflow/xy-ui'; import { Callout, Tabs } from 'nextra/components'; import { Embed, Image } from 'xy-shared'; import { RemoteCodeViewer } from 'xy-shared/server'; Today we'll be looking at how to create an interactive audio playground using React Flow and the Web Audio API. We'll start from scratch, first learning about the Web Audio API before looking at how to handle many common scenarios in React Flow: state management, implementing custom nodes, and adding interactivity. A screenshot of bleep.cafe, a visual audio programming environment. In it,
there are four nodes connected together: an xy pad, an oscillator node, a
volume node, and a master output. A while back I shared a project I was working on to the React Flow [discord server](https://discord.com/invite/RVmnytFmGW). It's called [bleep.cafe](https://bleep.cafe) and it's a little web app for learning digital synthesis all inside the browser. A lot of folks were interested to see how something like that was put together: most people don't even know **their browser has a whole synth engine built in!** This tutorial will take us step-by-step to build something similar. We may skip over some bits here and there, but for the most part if you're new to React Flow *or* the Web Audio API you should be able to follow along and have something working by the end. If you're already a React Flow wizard you might want to read the first section covering the Web Audio API and then jump to the third to see how things are tied together! But first... #### A demo! This and other examples in this tutorial *make sound*. To avoid creating an avant-garde masterpiece, remember to mute each example before moving on! #### The Web Audio API Before we get stuck in to React Flow and interactive node editor goodness, we need to take a crash course on the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API). Here are the highlights you need to know: * The Web Audio API provides a variety of different audio nodes, including sources (e.g. [OscillatorNode](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode), [MediaElementAudioSourceNode](https://developer.mozilla.org/en-US/docs/Web/API/MediaElementAudioSourceNode)), effects (e.g. [GainNode](https://developer.mozilla.org/en-US/docs/Web/API/GainNode), [DelayNode](https://developer.mozilla.org/en-US/docs/Web/API/DelayNode), [ConvolverNode](https://developer.mozilla.org/en-US/docs/Web/API/ConvolverNode)), and outputs (e.g. [AudioDestinationNode](https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode)). * Audio nodes can be connected together to form a (potentially cyclic) graph. We tend to call this the audio-processing graph, signal graph, or signal chain. * Audio processing is handled in a separate thread by native code. This means we can keep generating sounds even when the main UI thread is busy or blocked. * An [AudioContext](https://developer.mozilla.org/en-US/docs/Web/API/AudioContext) acts as the brain of an audio-processing graph. We can use it to create new audio nodes and suspend or resume audio processing entirely. ##### Hello, sound! Let's see some of this stuff in action and build our first Web Audio app! We won't be doing anything too wild: we'll make a simple mouse [theremin](http://www.thereminworld.com/Article/14232/what-s-a-theremin-). We'll use React for these examples and everything else moving forward (we're called React Flow after all!) and [`vite`](https://vitejs.dev) to handle bundling and hot reloading. If you prefer another bundler like parcel or Create React App that's cool too, they all do largely the same thing. You could also choose to use TypeScript instead of JavaScript. To keep things simple we won't use it today, but React Flow is fully typed (and written entirely in TypeScript) so it's a breeze to use! ```bash npm2yarn npm create vite@latest -- --template react ``` Vite will scaffold out a simple React application for us, but can delete the assets and jump right into `App.jsx`. Remove the demo component generated for us and start by creating a new AudioContext and putting together the nodes we need. We want an OscillatorNode to generate some tones and a GainNode to control the volume. ```js filename="./src/App.jsx" // Create the brain of our audio-processing graph const context = new AudioContext(); // Create an oscillator node to generate tones const osc = context.createOscillator(); // Create a gain node to control the volume const amp = context.createGain(); // Pass the oscillator's output through the gain node and to our speakers osc.connect(amp); amp.connect(context.destination); // Start generating those tones! osc.start(); ``` Oscillator nodes need to be started. Don't forget that call to `osc.start`. The oscillator won't start generating tones without it! For our app, we'll track the mouse's position on the screen and use that to set the pitch of the oscillator node and the volume of the gain node. ```jsx filename="./src/App.jsx" {12-27} import React from 'react'; const context = new AudioContext(); const osc = context.createOscillator(); const amp = context.createGain(); osc.connect(amp); amp.connect(context.destination); osc.start(); const updateValues = (e) => { const freq = (e.clientX / window.innerWidth) * 1000; const gain = e.clientY / window.innerHeight; osc.frequency.value = freq; amp.gain.value = gain; }; export default function App() { return
; } ``` `osc.frequency.value`, `amp.gain.value`... The Web Audio API makes a distinction between simple object properties and audio node *parameters*. That distinction appears in the form of an `AudioParam`. You can read up on them in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam) but for now it's enough to know that you need to use `.value` to set the value of an `AudioParam` rather than just assigning a value to the property directly. If you try this example as it is, you'll probably find that nothing happens. An AudioContext often starts in a suspended state in an attempt to avoid ads hijacking our speakers. We can fix that easily by adding a click handler on the `
` to resume the context if it's suspended. ```jsx filename="./src/App.jsx" {1-7,12} const toggleAudio = () => { if (context.state === 'suspended') { context.resume(); } else { context.suspend(); } }; export default function App() { return (
); }; ``` And that's everything we need to start making some sounds with the Web Audio API! Here's what we put together, in case you weren't following along at home: Now let's put this knowledge to one side and take a look at how to build a React Flow project from scratch. Already a React Flow pro? If you're already familiar with React Flow, you can comfortably skip over the next section and head straight on over to [making some sounds](#do-sound-to-it). For everyone else, let's take a look at how to build a React Flow project from scratch. #### Scaffolding a React Flow project Later on we'll take what we've learned about the Web Audio API, oscillators, and gain nodes and use React Flow to interactively build audio-processing graphs. For now though, we need to put together an empty React Flow app. We already have a React app set up with Vite, so we'll keep using that. If you skipped over the last section, we ran `npm create vite@latest -- --template react` to get started. You can use whatever bundler and/or dev server you like, though. Nothing here is vite specific. We only need three additional dependencies for this project: `@xyflow/react` for our UI (obviously!), `zustand` as our simple state management library (that's what we use under the hood at React Flow) and `nanoid` as a lightweight id generator. ```bash npm2yarn npm install @xyflow/react zustand nanoid ``` We're going to remove everything from our Web Audio crash course and start from scratch. Start by modifying `main.jsx` to match the following: ```jsx filename="./src/main.jsx" import App from './App'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { ReactFlowProvider } from '@xyflow/react'; // 👇 Don't forget to import the styles! import '@xyflow/react/dist/style.css'; import './index.css'; const root = document.querySelector('#root'); ReactDOM.createRoot(root).render( {/* React flow needs to be inside an element with a known height and width to work */}
, ); ``` There are three important things to pay attention to here: 1. You need to remember to **import the React Flow CSS styles** to make sure everything works correctly. 2. The React Flow renderer needs to be inside an element with a known height and width, so we've set the containing `
` to take up the entire screen. 3. To use some of the hooks React Flow provides, your components need to be inside a `` or inside the `` component itself, so we've wrapped the entire app in the provider to be sure. Next, hop into `App.jsx` and create an empty flow: ```jsx filename="./src/App.jsx" import React from 'react'; import { ReactFlow, Background } from '@xyflow/react'; export default function App() { return ( ); } ``` We'll expand and add on to this component over time. For now, we've added one of React Flow's built-in components - [``](/api-reference/components/background) - to check if everything is setup correctly. Go ahead and run `npm run dev` (or whatever you need to do to spin up a dev server if you didn't choose vite) and check out your browser. You should see an empty flow: Screenshot of an empty React Flow graph Leave the dev server running. We can keep checking back on our progress as we add new bits and bobs. ##### 1. State management with Zustand A Zustand store will hold all the UI state for our application. In practical terms that means it'll hold the nodes and edges of our React Flow graph, a few other pieces of state, and a handful of *actions* to update that state. To get a basic interactive React Flow graph going we need three actions: 1. `onNodesChange` to handle nodes being moved around or deleted. 2. `onEdgesChange` to handle *edges* being moved around or deleted. 3. `addEdge` to connect two nodes in the graph. Go ahead and create a new file, `store.js`, and add the following: ```js filename="./src/store.js" import { applyNodeChanges, applyEdgeChanges } from '@xyflow/react'; import { nanoid } from 'nanoid'; import { createWithEqualityFn } from 'zustand/traditional'; export const useStore = createWithEqualityFn((set, get) => ({ nodes: [], edges: [], onNodesChange(changes) { set({ nodes: applyNodeChanges(changes, get().nodes), }); }, onEdgesChange(changes) { set({ edges: applyEdgeChanges(changes, get().edges), }); }, addEdge(data) { const id = nanoid(6); const edge = { id, ...data }; set({ edges: [edge, ...get().edges] }); }, })); ``` Zustand is dead simple to use. We create a function that receives both a `set` and a `get` function and returns an object with our initial state along with the actions we can use to update that state. Updates happen immutably and we can use the `set` function for that. The `get` function is how we read the current state. And... that's it for zustand. The `changes` argument in both `onNodesChange` and `onEdgesChange` represents events like a node or edge being moved or deleted. Fortunately, React Flow provides some [helper](/api-reference/utils/apply-node-changes) [functions](/api-reference/utils/apply-edge-changes) to apply those changes for us. We just need to update the store with the new array of nodes. `addEdge` will be called whenever two nodes get connected. The `data` argument is *almost* a valid edge, it's just missing an id. Here we're getting nanoid to generate a 6 character random id and then adding the edge to our graph, nothing exciting. If we hop back over to our `` component we can hook React Flow up to our actions and get something working. ```jsx filename="./src/App.jsx" {3,5,7-13,16,20-24} import React from 'react'; import { ReactFlow, Background } from '@xyflow/react'; import { shallow } from 'zustand/shallow'; import { useStore } from './store'; const selector = (store) => ({ nodes: store.nodes, edges: store.edges, onNodesChange: store.onNodesChange, onEdgesChange: store.onEdgesChange, addEdge: store.addEdge, }); export default function App() { const store = useStore(selector, shallow); return ( ); } ``` So what's this `selector` thing all about? Zustand let's us supply a selector function to pluck out the exact bits of state we need from the store. Combined with the `shallow` equality function, this means we typically don't have re-renders when state we don't care about changes. Right now, our store is small and we actually want everything from it to help render our React Flow graph, but as we expand on it this selector will make sure we're not re-rendering *everything* all the time. This is everything we need to have an interactive graph: we can move nodes around, connect them together, and remove them. To demonstrate, *temporarily* add some dummy nodes to your store: ```js filename="./store.jsx" {2-6} const useStore = createWithEqualityFn((set, get) => ({ nodes: [ { id: 'a', data: { label: 'oscillator' }, position: { x: 0, y: 0 } }, { id: 'b', data: { label: 'gain' }, position: { x: 50, y: 50 } }, { id: 'c', data: { label: 'output' }, position: { x: -50, y: 100 } } ], ... })); ``` ##### 2. Custom nodes OK great, we have an interactive React Flow instance we can start playing with. We added some dummy nodes but they're just the default unstyled ones right now. In this step we'll add three custom nodes with interactive controls: 1. An oscillator node and controls for the pitch and waveform type. 2. A gain node and a control for the volume 3. An output node and a button to toggle audio processing on and off. Let's create a new folder, `nodes/`, and create a file for each custom node we want to create. Starting with the oscillator we need two controls and a source handle to connect the output of the oscillator to other nodes. ```jsx filename="./src/nodes/Osc.jsx" import React from 'react'; import { Handle } from '@xyflow/react'; import { useStore } from '../store'; export default function Osc({ id, data }) { return (

Oscillator Node

); }; ``` "nodrag" is important. Pay attention to the `"nodrag"` class being added to both the `` and ` {data.frequency}Hz
); } ``` Hey, that `selector` is back! Notice how this time we're using it to derive two event handlers, `setFrequency` and `setType`, from the general `updateNode` action. The last piece of the puzzle is to tell React Flow how to render our custom node. For that we need to create a `nodeTypes` object: the keys should correspond to a node's `type` and the value will be the React component to render. ```jsx filename="./src/App.jsx" {5,16-18,26} import React from 'react'; import { ReactFlow } from '@xyflow/react'; import { shallow } from 'zustand/shallow'; import { useStore } from './store'; import Osc from './nodes/Osc'; const selector = (store) => ({ nodes: store.nodes, edges: store.edges, onNodesChange: store.onNodesChange, onEdgesChange: store.onEdgesChange, addEdge: store.addEdge, }); const nodeTypes = { osc: Osc, }; export default function App() { const store = useStore(selector, shallow); return ( ); } ``` Avoid unnecessary renders. It's important to define `nodeTypes` outside of the `` component (or use React's [`useMemo`](https://react.dev/reference/react/useMemo)) to avoid recomputing it every render. If you've got the dev server running, don't panic if things haven't changed yet! None of our temporary nodes have been given the right type yet, so React Flow just falls back to rendering the default node. If we change one of those nodes to be an `osc` with some initial values for `frequency` and `type` we should see our custom node being rendered. ```js title"./src/store.js" const useStore = createWithEqualityFn((set, get) => ({ nodes: [ { type: 'osc', id: 'a', data: { frequency: 220, type: 'square' }, position: { x: 0, y: 0 } }, ... ], ... })); ``` Stuck on styling? If you're just implementing the code from this post as you go along, you'll see that your custom node doesn't look like the one in the preview above. To keep things easy to digest, we've left out styling in the code snippets. To learn how to style your custom nodes, check out our docs on [theming](/learn/customization/theming) or our example using [Tailwind](/examples/styling/tailwind). Implementing a gain node is pretty much the same process, so we'll leave that one to you. Instead, we'll turn our attention to the output node. This node will have no parameters control, but we do want to toggle signal processing on and off. That's a bit difficult right now when we haven't implemented any audio code yet, so in the meantime we'll add just a flag to our store and an action to toggle it. ```js filename="./src/store.js" const useStore = createWithEqualityFn((set, get) => ({ ... isRunning: false, toggleAudio() { set({ isRunning: !get().isRunning }); }, ... })); ``` The custom node itself is then pretty simple: ```jsx filename="./src/nodes/Out.jsx" import React from 'react'; import { Handle } from '@xyflow/react'; import { shallow } from 'zustand/shallow'; import { useStore } from '../store'; const selector = (store) => ({ isRunning: store.isRunning, toggleAudio: store.toggleAudio, }); export default function Out({ id, data }) { const { isRunning, toggleAudio } = useStore(selector, shallow); return (

Output Node

); } ``` Things are starting to shape up quite nicely! The next step, then, is to… #### Do sound to it We have an interactive graph and we're able to update node data, now let's add in what we know about the Web Audio API. Start by creating a new file, `audio.js`, and create a new audio context and an empty `Map`. ```js filename="./src/audio.js" const context = new AudioContext(); const nodes = new Map(); ``` The way we'll manage our audio graph is by hooking into the different actions in our store. So we might connect two audio nodes when the `addEdge` action is called, or update an audio node's properties when `updateNode` is called, and so on. Hardcoded nodes We hardcoded a couple of nodes in our store earlier on in this post but our audio graph doesn't know anything about them! For the finished project we can do away with all these hardcoded bits, but for now it's **really important** that we also hardcode some audio nodes. Here's how we did it: ```js filename="./src/audio.js" {4-7,9-10,12,14-16} const context = new AudioContext(); const nodes = new Map(); const osc = context.createOscillator(); osc.frequency.value = 220; osc.type = 'square'; osc.start(); const amp = context.createGain(); amp.gain.value = 0.5; const out = context.destination; nodes.set('a', osc); nodes.set('b', amp); nodes.set('c', out); ``` ##### 1. Node changes Right now, there are two types of node changes that can happen in our graph and that we need to respond to: updating a node's `data`, and removing a node from the graph. We already have an action for the former, so let's handle that first. In `audio.js` we'll define a function, `updateAudioNode`, that we'll call with a node's id and a partial `data` object and use it to update an existing node in the `Map`: ```js filename="./src/audio.js" export function updateAudioNode(id, data) { const node = nodes.get(id); for (const [key, val] of Object.entries(data)) { if (node[key] instanceof AudioParam) { node[key].value = val; } else { node[key] = val; } } } ``` Remember that properties on an audio node may be special `AudioParams` that must be updated differently to regular object properties. Now we'll want to update our `updateNode` action in the store to call this function as part of the update: ```js filename="./src/store.js" import { updateAudioNode } from './audio'; export const useStore = createWithEqualityFn((set, get) => ({ ... updateNode(id, data) { updateAudioNode(id, data); set({ nodes: ... }); }, ... })); ``` The next change we need to handle is removing a node from the graph. If you select a node in the graph and hit backspace, React Flow will remove it. This is implicitly handled for us by the `onNodesChange` action we hooked up, but now we want some additional handling we'll need to wire up a new action to React Flow's `onNodesDelete` event. This is actually pretty simple, so I'll save you some reading and present the next three snippets of code without comment. ```js export function removeAudioNode(id) { const node = nodes.get(id); node.disconnect(); node.stop?.(); nodes.delete(id); } ``` ```js import { ..., removeAudioNode } from './audio'; export const useStore = createWithEqualityFn((set, get) => ({ ... removeNodes(nodes) { for (const { id } of nodes) { removeAudioNode(id) } }, ... })); ``` ```jsx const selector = store => ({ ..., onNodesDelete: store.removeNodes }); export default function App() { const store = useStore(selector, shallow); return ( ) }; ``` The only thing to note is that `onNodesDelete` calls the provided callback with an *array* of deleted nodes, because it is possible to delete more than one node at once! ##### 2. Edge changes We're getting super close to actually making some sounds! All that's left is to handle changes to our graph's edges. Like with node changes, we already have an action to handle creating new edges and we're also implicitly handling removed edges in `onEdgesChange`. To handle new connections, we just need the `source` and `target` ids from the edge created in our `addEdge` action. Then we can just look up the two nodes in our `Map` and connect them up. ```js export function connect(sourceId, targetId) { const source = nodes.get(sourceId); const target = nodes.get(targetId); source.connect(target); } ``` ```js import { ..., connect } from './audio'; export const useStore = createWithEqualityFn((set, get) => ({ ... addEdge(data) { ... connect(data.source, data.target); }, ... })); ``` We saw React Flow accepted an `onNodesDelete` handler and wouldn't you know it, there's an `onEdgesDelete` handler too! The approach we'd take to implement `disconnect` and hook it up to our store and React Flow instance is pretty much the same as before, so we'll leave that one down to you as well! ##### 3. Switching the speakers on You'll remember that our `AudioContext` probably begins in a suspended state to prevent potentially annoying autoplay issues. We already faked the data and actions we need for our `` component in the store, now we just need to replace them with the real context's state and resume/suspend methods. ```js filename="./src/audio.js" export function isRunning() { return context.state === 'running'; } export function toggleAudio() { return isRunning() ? context.suspend() : context.resume(); } ``` Although we haven't been returning anything from our audio functions up until now, we need to return from `toggleAudio` because those methods are asynchronous and we don't want to update the store prematurely! ```js filename="./src/store.js" import { ..., isRunning, toggleAudio } from './audio' export const useStore = createWithEqualityFn((set, get) => ({ ... isRunning: isRunning(), toggleAudio() { toggleAudio().then(() => { set({ isRunning: isRunning() }); }); } })); ``` Et voilà, we did it! We've now put enough together to actually *make sounds*! Let's see what we have in action. ##### 4. Creating new nodes Up until now we have been dealing with a hard-coded set of nodes in our graph. This has been fine for prototyping but for it to actually be useful we'll want a way to add new nodes to the graph dynamically. Our final task will be adding this functionality: we'll work backwards starting with the audio code and ending by creating a basic toolbar. Implementing a `createAudioNode` function will be simple enough. All we need is an id for the new node, the type of node to create, and its initial data: ```js filename="./src/audio.js" export function createAudioNode(id, type, data) { switch (type) { case 'osc': { const node = context.createOscillator(); node.frequency.value = data.frequency; node.type = data.type; node.start(); nodes.set(id, node); break; } case 'amp': { const node = context.createGain(); node.gain.value = data.gain; nodes.set(id, node); break; } } } ``` Next we'll need a `createNode` function in our store. The node id will be generated by nanoid and we'll hardcode some initial data for each of the node types, so the only thing we need to pass in is the type of node to create: ```js filename="./src/store.js" import { ..., createAudioNode } from './audio'; export const useStore = createWithEqualityFn((set, get) => ({ ... createNode(type) { const id = nanoid(); switch(type) { case 'osc': { const data = { frequency: 440, type: 'sine' }; const position = { x: 0, y: 0 }; createAudioNode(id, type, data); set({ nodes: [...get().nodes, { id, type, data, position }] }); break; } case 'amp': { const data = { gain: 0.5 }; const position = { x: 0, y: 0 }; createAudioNode(id, type, data); set({ nodes: [...get().nodes, { id, type, data, position }] }); break; } } } })); ``` We could be a bit smarter about calculating the position of the new node, but to keep things simple we'll just hardcode it to `{ x: 0, y: 0 }` for now. The final piece of the puzzle is to create a toolbar component that can trigger the new `createNode` action. To do that we'll jump back to `App.jsx` and make use of the [``](/docs//api-reference/components/panel/) built-in component. ```jsx filename="./src/App.jsx" ... import { ReactFlow, Panel } from '@xyflow/react'; ... const selector = (store) => ({ ..., createNode: store.createNode, }); export default function App() { const store = useStore(selector, shallow); return ( ... ); }; ``` We don't need anything fancy here, just a couple of buttons that trigger the `createNode` action with the appropriate type: ```jsx filename="./src/App.jsx" ``` And that's... everything! We've now got a fully functional audio graph editor that can: * Create new audio nodes * Update node data with some UI controls * Connect nodes together * Delete nodes and connections * Start and stop audio processing Here's the demo from the beginning, but this time you can see the source code to make sure you haven't missed anything. #### Final thoughts Whew that was a long one, but we made it! For our efforts we've come out the other side with a fun little interactive audio playground, learned a little bit about the Web Audio API along the way, and have a better idea of one approach to "running" a React Flow graph. If you've made it this far and are thinking "Hayleigh, I'm never going to write a Web Audio app. Did I learn *anything* useful?" Then you're in luck, because you did! You could take our approach to connecting to the Web Audio API and apply it to some other graph-based computation engine like [behave-graph](https://github.com/bhouston/behave-graph). In fact, some has done just that and created [behave-flow](https://github.com/beeglebug/behave-flow)! There are still plenty of ways to expand on this project. If you'd like to keep working on it, here are some ideas: * Add more node types. * Allow nodes to connect to `AudioParams` on other nodes. * Use the [`AnalyserNode`](https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode) to visualize the output of a node or signal. * Anything else you can think of! And if you're looking for inspiration, there are quite a few projects out in the wild that are using node-based UIs for audio things. Some of my favorites are [Max/MSP](https://cycling74.com/products/max/), [Reaktor](https://www.native-instruments.com/en/products/komplete/synths/reaktor-6/), and [Pure Data](https://puredata.info/). Max and Reaktor are closed-source commercial software, but you can still steal some ideas from them . You can use the completed [source code](https://github.com/xyflow/react-flow-web-audio) as a starting point, or you can just keep building on top of what we've made today. We'd love to see what you build so please share it with us over on our [Discord server](https://discord.com/invite/RVmnytFmGW) or [Twitter](https://twitter.com/xyflowdev). React Flow is an independent company financed by its users. If you want to support us you can [sponsor us on Github](https://github.com/sponsors/xyflow) or [subscribe to one of our Pro plans](/pro/). ### Create a slide show presentation with React Flow import { Emoji } from '@xyflow/xy-ui'; import { Callout, Cards } from 'nextra/components'; import { Image } from 'xy-shared'; import { RemoteCodeViewer } from 'xy-shared/server'; We recently published the findings from our React Flow 2023 end-of-year survey with an [interactive presentation](/developer-survey-2023) of the key findings, using React Flow itself. There were lots of useful bits built into this slideshow app, so we wanted to share how we built it! Screenshot of slides laid out on an infinite canvas, each with information pulled from a survey of React Flow users By the end of this tutorial, you will have built a presentation app with * Support for markdown slides * Keyboard navigation around the viewport * Automatic layouting * Click-drag panning navigation (à la Prezi) Along the way, you'll learn a bit about the basics of layouting algorithms, creating static flows, and custom nodes. Once you're done, the app will look like this! To follow along with this tutorial we'll assume you have a basic understanding of [React](https://reactjs.org/docs/getting-started.html) and [React Flow](/learn/concepts/terms-and-definitions), but if you get stuck on the way feel free to reach out to us on [Discord](https://discord.com/invite/RVmnytFmGW)! Here's the [repo with the final code](https://github.com/xyflow/react-flow-slide-show) if you'd like to skip ahead or refer to it as we go. Let's get started! #### Setting up the project We like to recommend using [Vite](https://vitejs.dev) when starting new React Flow projects, and this time we'll use TypeScript too. You can scaffold a new project with the following command: ```bash npm2yarn npm create vite@latest -- --template react-ts ``` If you'd prefer to follow along with JavaScript feel free to use the `react` template instead. You can also follow along in your browser by using our Codesandbox templates: JS
} /> TS
} /> Besides React Flow we only need to pull in one dependency, [`react-remark`](https://www.npmjs.com/package/react-remark), to help us render markdown in our slides. ```bash npm2yarn npm install @xyflow/react react-remark ``` We'll modify the generated `main.tsx` to include React Flow's styles, as well as wrap the app in a `` to make sure we can access the React Flow instance inside our components; ```tsx filename="main.tsx" {3,7,12,20} import React from 'react'; import ReactDOM from 'react-dom/client'; import { ReactFlowProvider } from '@xyflow/react'; import App from './App'; import '@xyflow/react/dist/style.css'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( {/* The parent element of the React Flow component needs a width and a height to work properly. If you're styling your app as you follow along, you can remove this div and apply styles to the #root element in your CSS. */}
, ); ``` This tutorial is going to gloss over the styling of the app, so feel free to use any CSS framework or styling solution you're familiar with. If you're going to style your app differently from just writing CSS, [Tailwind CSS](/examples/styling/tailwind), you can skip the import to `index.css`. How you style your app is up to you, but you must **always** include React Flow's styles! If you don't need the default styles, at a minimum you should include the base styles from `@xyflow/react/dist/base.css`. Each slide of our presentation will be a node on the canvas, so let's create a new file `Slide.tsx` that will be our custom node used to render each slide. ```tsx filename="Slide.tsx" import { type Node, type NodeProps } from '@xyflow/react'; export const SLIDE_WIDTH = 1920; export const SLIDE_HEIGHT = 1080; export type SlideNode = Node; export type SlideData = {}; const style = { width: `${SLIDE_WIDTH}px`, height: `${SLIDE_HEIGHT}px`, } satisfies React.CSSProperties; export function Slide({ data }: NodeProps) { return (
Hello, React Flow!
); } ``` We're setting the slide width and height as constants here (rather than styling the node in CSS) because we'll want access to those dimensions later on. We've also stubbed out the `SlideData` type so we can properly type the component's props. The last thing to do is to register our new custom node and show something on the screen. ```tsx filename="App.tsx" import { ReactFlow } from '@xyflow/react'; import { Slide } from './Slide.tsx'; const nodeTypes = { slide: Slide, }; export default function App() { const nodes = [{ id: '0', type: 'slide', position: { x: 0, y: 0 }, data: {} }]; return ; } ``` It's important to remember to define your `nodeTypes` object *outside* of the component (or to use React's `useMemo` hook)! When the `nodeTypes` object changes, the entire flow is re-rendered. With the basics put together, you can start the development server by running `npm run dev` and see the following: Not super exciting yet, but let's add markdown rendering and create a few slides side by side! #### Rendering markdown We want to make it easy to add content to our slides, so we'd like the ability to write [Markdown](https://www.markdownguide.org/basic-syntax/) in our slides. If you're not familiar, Markdown is a simple markup language for creating formatted text documents. If you've ever written a README on GitHub, you've used Markdown! Thanks to the `react-remark` package we installed earlier, this step is a simple one. We can use the `` component to render a string of markdown content into our slides. ```tsx filename="Slide.tsx" {2,9-11,21} import { type Node, type NodeProps } from '@xyflow/react'; import { Remark } from 'react-remark'; export const SLIDE_WIDTH = 1920; export const SLIDE_HEIGHT = 1080; export type SlideNode = Node; export type SlideData = { source: string; }; const style = { width: `${SLIDE_WIDTH}px`, height: `${SLIDE_HEIGHT}px`, } satisfies React.CSSProperties; export function Slide({ data }: NodeProps) { return (
{data.source}
); } ``` In React Flow, nodes can have data stored on them that can be used during rendering. In this case we're storing the markdown content to display by adding a `source` property to the `SlideData` type and passing that to the `` component. We can update our hardcoded nodes with some markdown content to see it in action: ```tsx filename="App.tsx" {2, 10-27, 34} import { ReactFlow } from '@xyflow/react'; import { Slide, SLIDE_WIDTH } from './Slide'; const nodeTypes = { slide: Slide, }; export default function App() { const nodes = [ { id: '0', type: 'slide', position: { x: 0, y: 0 }, data: { source: '# Hello, React Flow!' }, }, { id: '1', type: 'slide', position: { x: SLIDE_WIDTH, y: 0 }, data: { source: '...' }, }, { id: '2', type: 'slide', position: { x: SLIDE_WIDTH * 2, y: 0 }, data: { source: '...' }, }, ]; return ; } ``` Note that we've added the `minZoom` prop to the `` component. Our slides are quite large, and the default minimum zoom level is not enough to zoom out and see multiple slides at once. In the nodes array above, we've made sure to space the slides out by doing some manual math with the `SLIDE_WIDTH` constant. In the next section we'll come up with an algorithm to automatically lay out the slides in a grid. #### Laying out the nodes We often get asked how to automatically lay out nodes in a flow, and we have some documentation on how to use common layouting libraries like dagre and d3-hierarchy in our [layouting guide](/learn/layouting/layouting). Here you'll be writing your own super-simple layouting algorithm, which gets a bit nerdy, but stick with us! For our presentation app we'll construct a simple grid layout by starting from 0,0 and updating the x or y coordinates any time we have a new slide to the left, right, up, or down. First, we need to update our `SlideData` type to include optional ids for the slides to the left, right, up, and down of the current slide. ```tsx filename="Slide.tsx" {3-6} export type SlideData = { source: string; left?: string; up?: string; down?: string; right?: string; }; ``` Storing this information on the node data directly gives us some useful benefits: * We can write fully declarative slides without worrying about the concept of nodes and edges * We can compute the layout of the presentation by visiting connecting slides * We can add navigation buttons to each slide to navigate between them automatically. We'll handle that in a later step. The magic happens in a function we're going to define called `slidesToElements`. This function will take an object containing all our slides addressed by their id, and an id for the slide to start at. Then it will work through each connecting slide to build an array of nodes and edges that we can pass to the `` component. The algorithm will go something like this: * Push the initial slide's id and the position `{ x: 0, y: 0 }` onto a stack. * While that stack is not empty... * Pop the current position and slide id off the stack. * Look up the slide data by id. * Push a new node onto the nodes array with the current id, position, and slide data. * Add the slide's id to a set of visited slides. * For every direction (left, right, up, down)... * Make sure the slide has not already been visited. * Take the current position and update the x or y coordinate by adding or subtracting `SLIDE_WIDTH` or `SLIDE_HEIGHT` depending on the direction. * Push the new position and the new slide's id onto a stack. * Push a new edge onto the edges array connecting the current slide to the new slide. * Repeat for the remaining directions... If all goes to plan, we should be able to take a stack of slides shown below and turn them into a neatly laid out grid! Let's see the code. In a file called `slides.ts` add the following: ```tsx filename="slides.ts" import { SlideData, SLIDE_WIDTH, SLIDE_HEIGHT } from './Slide'; export const slidesToElements = (initial: string, slides: Record) => { // Push the initial slide's id and the position `{ x: 0, y: 0 }` onto a stack. const stack = [{ id: initial, position: { x: 0, y: 0 } }]; const visited = new Set(); const nodes = []; const edges = []; // While that stack is not empty... while (stack.length) { // Pop the current position and slide id off the stack. const { id, position } = stack.pop(); // Look up the slide data by id. const data = slides[id]; const node = { id, type: 'slide', position, data }; // Push a new node onto the nodes array with the current id, position, and slide // data. nodes.push(node); // add the slide's id to a set of visited slides. visited.add(id); // For every direction (left, right, up, down)... // Make sure the slide has not already been visited. if (data.left && !visited.has(data.left)) { // Take the current position and update the x or y coordinate by adding or // subtracting `SLIDE_WIDTH` or `SLIDE_HEIGHT` depending on the direction. const nextPosition = { x: position.x - SLIDE_WIDTH, y: position.y, }; // Push the new position and the new slide's id onto a stack. stack.push({ id: data.left, position: nextPosition }); // Push a new edge onto the edges array connecting the current slide to the // new slide. edges.push({ id: `${id}->${data.left}`, source: id, target: data.left }); } // Repeat for the remaining directions... } return { nodes, edges }; }; ``` We've left out the code for the right, up, and down directions for brevity, but the logic is the same for each direction. We've also included the same breakdown of the algorithm as comments, to help you navigate the code. Below is a demo app of the layouting algorithm, you can edit the `slides` object to see how adding slides to different directions affects the layout. For example, try extending 4's data to include `down: '5'` and see how the layout updates. If you spend a little time playing with this demo, you'll likely run across two limitations of this algorithm: 1. It is possible to construct a layout that overlaps two slides in the same position. 2. The algorithm will ignore nodes that cannot be reached from the initial slide. Addressing these shortcomings is totally possible, but a bit beyond the scope of this tutorial. If you give a shot, be sure to share your solution with us on the [discord server](https://discord.com/invite/RVmnytFmGW)! With our layouting algorithm written, we can hop back to `App.tsx` and remove the hardcoded nodes array in favor of the new `slidesToElements` function. ```tsx filename="App.tsx" {2,3,5-9,15-16,24} import { ReactFlow } from '@xyflow/react'; import { slidesToElements } from './slides'; import { Slide, SlideData, SLIDE_WIDTH } from './Slide'; const slides: Record = { '0': { source: '# Hello, React Flow!', right: '1' }, '1': { source: '...', left: '0', right: '2' }, '2': { source: '...', left: '1' }, }; const nodeTypes = { slide: Slide, }; const initialSlide = '0'; const { nodes, edges } = slidesToElements(initialSlide, slides); export default function App() { return ( ); } ``` The slides in our flow are static, so we can move the `slidesToElements` call *outside* the component to make sure we're not recalculating the layout if the component re-renders. Alternatively, you could use React's `useMemo` hook to define things inside the component but only calculate them once. Because we have the idea of an "initial" slide now, we're also using the `fitViewOptions` to ensure the initial slide is the one that is focused when the canvas is first loaded. #### Navigating between slides So far we have our presentation laid out in a grid but we have to manually pan the canvas to see each slide, which isn't very practical for a presentation! We're going to add three different ways to navigate between slides: * Click-to-focus on nodes for jumping to different slides by clicking on them. * Navigation buttons on each slide for moving sequentially between slides in any valid direction. * Keyboard navigation using the arrow keys for moving around the presentation without using the mouse or interacting with a slide directly. ##### Focus on click The `` element can receive an [`onNodeClick`](/api-reference/react-flow#on-node-click) callback that fires when *any* node is clicked. Along with the mouse event itself, we also receive a reference to the node that was clicked on, and we can use that to pan the canvas thanks to the `fitView` method. [`fitView`](/api-reference/types/react-flow-instance#fit-view) is a method on the React Flow instance, and we can get access to it by using the [`useReactFlow`](/api-reference/types/react-flow-instance#use-react-flow) hook. ```tsx filename="App.tsx" {1-2,17-23,29} import { useCallback } from 'react'; import { ReactFlow, useReactFlow, type NodeMouseHandler } from '@xyflow/react'; import { Slide, SlideData, SLIDE_WIDTH } from './Slide'; const slides: Record = { ... } const nodeTypes = { slide: Slide, }; const initialSlide = '0'; const { nodes, edges } = slidesToElements(initialSlide, slides); export default function App() { const { fitView } = useReactFlow(); const handleNodeClick = useCallback( (_, node) => { fitView({ nodes: [node], duration: 150 }); }, [fitView], ); return ( ); } ``` It's important to remember to include `fitView` as in the dependency array of our `handleNodeClick` callback. That's because the `fitView` function is replaced once React Flow has initialized the viewport. If you forget this step you'll likely find out that `handleNodeClick` does nothing at all (and yes, we also forget this ourselves sometimes too ). Calling `fitView` with no arguments would attempt to fit every node in the graph into view, but we only want to focus on the node that was clicked! The [`FitViewOptions`](/api-reference/types/fit-view-options) object lets us provide an array of just the nodes we want to focus on: in this case, that's just the node that was clicked. ##### Slide controls Clicking to focus a node is handy for zooming out to see the big picture before focusing back in on a specific slide, but it's not a very practical way for navigating around a presentation. In this step we'll add some controls to each slide that allow us to move to a connected slide in any direction. Let's add a `