Migrate to v10
Welcome to React Flow v10! With the major version update, there are coming many new features but also some breaking changes.
New Features
- Sub Flows: You can now add nodes to a parent node and create groups and nested flows
- Node Type 'group': A new node type without handles that can be used as a group node
- Touch Device Support: It is now possible to connect nodes on touch devices
- Fit View on Init: You can use the new
fitView
prop to fit the initial view - Key Handling: Not only single keys but also multiple keys and key combinations are possible now
- useKeyPress hook: A util hook for handling keyboard events
- useReactFlow hook: Returns a React Flow instance that exposes functions to manipulate the flow
- useNodes, useEdges and useViewport hooks: Hooks for receiving nodes, edges and the viewport
- Edge Marker: More options to configure the start and end markers of an edge
Breaking Changes
TLDR:
- Split the
elements
array intonodes
andedges
arrays and implementonNodesChange
andonEdgesChange
handlers (detailed guide in the core concepts section) - Memoize your custom
nodeTypes
andedgeTypes
- Rename
onLoad
toonInit
- Rename
paneMoveable
topanOnDrag
- Rename
useZoomPanHelper
touseReactFlow
(andsetTransform
tosetViewport
) - Rename node and edge option
isHidden
tohidden
Detailed explanation of breaking changes:
1. Elements - Nodes and Edges
We saw that a lot of people struggle with the semi controlled elements
prop. It was always a bit messy to sync the local user state with the internal state of React Flow. Some of you used the internal store that was never documented and always a kind of hacky solution. For the new version we offer two ways to use React Flow - uncontrolled and controlled.
1.1. Controlled nodes
and edges
If you want to have the full control and use nodes and edges from your local state or your store, you can use the nodes
, edges
props in combination with the onNodesChange
and onEdgesChange
handlers. You need to implement these handlers for an interactive flow (if you are fine with just pan and zoom you don't need them). You'll receive a change when a node(s) gets initialized, dragged, selected or removed. This means that you always know the exact position and dimensions of a node or if it's selected for example. We export the helper functions applyNodeChanges
and applyEdgeChanges
that you should use to apply the changes.
Old API
import { useState, useCallback } from 'react';
import ReactFlow, { removeElements, addEdge } from 'react-flow-renderer';
const initialElements = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
{ id: 'e1-2', source: '1', target: '2' },
];
const BasicFlow = () => {
const [elements, setElements] = useState(initialElements);
const onElementsRemove = useCallback(
(elementsToRemove) =>
setElements((els) => removeElements(elementsToRemove, els)),
[],
);
const onConnect = useCallback((connection) =>
setElements((es) => addEdge(connection, es)),
);
return (
<ReactFlow
elements={elements}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
/>
);
};
export default BasicFlow;
New API
import { useState, useCallback } from 'react';
import ReactFlow, {
applyNodeChanges,
applyEdgeChanges,
addEdge,
} from 'react-flow-renderer';
const initialNodes = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
const BasicFlow = () => {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const onNodesChange = useCallback(
(changes) => setNodes((ns) => applyNodeChanges(changes, ns)),
[],
);
const onEdgesChange = useCallback(
(changes) => setEdges((es) => applyEdgeChanges(changes, es)),
[],
);
const onConnect = useCallback((connection) =>
setEdges((eds) => addEdge(connection, eds)),
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
/>
);
};
export default BasicFlow;
You can also use the new hooks useNodesState
and useEdgesState
for a quick start:
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
related changes:
onElementsClick
->onNodeClick
andonEdgeClick
onElementsRemove
-> replaced by theonNodesChange
andonEdgesChange
handler
1.2 Uncontrolled defaultNodes
and defaultEdges
The easiest way to get started is to use the defaultNodes
and defaultEdges
props. When you set these props, all actions are handled internally. You don't need to add any other handlers to get a fully interactive flow with the ability to drag nodes, connect nodes and remove nodes and edges:
New API
import ReactFlow from 'react-flow-renderer';
const defaultNodes = [
{ id: '1', data: { label: 'Node 1' }, position: { x: 250, y: 0 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } },
];
const defaultEdges = [{ id: 'e1-2', source: '1', target: '2' }];
const BasicFlow = () => {
return <ReactFlow defaultNodes={defaultNodes} defaultEdges={defaultEdges} />;
};
export default BasicFlow;
If you want to add, remove or update a node or edge you can only do this by using the ReactFlow instance that you can receive either with the new useReactFlow
hook or by using the onInit
handler that gets the instance as a function param.
2. Memoize your custom nodeTypes
and edgeTypes
Whenever you pass new node or edge types, we create wrapped node or edge component types in the background. This means that you should not create a new nodeType
or edgeType
object on every render. Memoize your nodeTypes and edgeTypes or define them outside of the component when they don't change.
Don't do this:
This creates a new object on every render and leads to bugs and performance issues:
// this is bad! Don't do it.
<ReactFlow
nodes={[]}
nodeTypes={{
specialType: SpecialNode, // bad!
}}
/>
Do this:
function Flow() {
const nodeTypes = useMemo(() => ({ specialType: SpecialNode }), []);
return <ReactFlow nodes={[]} nodeTypes={nodeTypes} />;
}
or create the types outside of the component when they don't change:
const nodeTypes = { specialType: SpecialNode };
function Flow() {
return <ReactFlow nodes={[]} nodeTypes={nodeTypes} />;
}
3. Redux - Zustand
We switched our state management library from Redux to Zustand (opens in a new tab). With this change we could remove about 300LOC from our state related code. If you need to access the internal store, you can use the useStore
hook:
Old API
import { useStoreState, useStoreActions } from 'react-flow-renderer';
...
const transform = useStoreState((store) => store.transform);
New API
import { useStore } from 'react-flow-renderer';
...
const transform = useStore((store) => store.transform);
You still need to wrap your component with the <ReactFlowProvider />
if you want to access the internal store.
We are also exporting useStoreApi
if you need to get the store in an event handler for example without triggering re-renders.
import { useStoreApi } from 'react-flow-renderer';
...
const store = useStoreApi();
...
// in an event handler
const [x, y, zoom] = store.getState().transform;
4. onLoad - onInit
The onLoad
callback has been renamed to onInit
and now fires when the nodes are initialized.
Old API
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
...
onLoad={onLoad}
/>
New API
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.zoomTo(2);
...
<ReactFlow
...
onInit={onInit}
/>
5. paneMoveable - panOnDrag
This is more consistent with the rest of the API (panOnScroll
, zoomOnScroll
, etc.)
Old API
<ReactFlow
...
paneMoveable={false}
/>
New API
<ReactFlow
...
panOnDrag={false}
/>
6. useZoomPanHelper transform - unified in useReactFlow
As "transform" is also the variable name of the transform in the store and it's not clear that transform
is a setter we renamed it to setViewport
. This is also more consistent with the other functions. Also, all useZoomPanHelper
functions have been moved to the React Flow instance that you get from the useReactFlow
hook or the onInit
handler.
Old API
const { transform, setCenter, setZoom } = useZoomPanHelper();
...
transform({ x: 100, y: 100, zoom: 2 });
New API
const { setViewport, setCenter, setZoom } = useReactFlow();
...
setViewport({ x: 100, y: 100, zoom: 2 });
New viewport functions:
getZoom
getViewport
7. isHidden - hidden
We mixed prefixed (is...
) and non-prefixed boolean option names. All node and edge options are not prefixed anymore. So it's hidden
, animated
, selected
, draggable
, selectable
and connectable
.
Old API
const hiddenNode = { id: '1', isHidden: true, position: { x: 50, y: 50 } };
New API
const hiddenNode = { id: '1', hidden: true, position: { x: 50, y: 50 } };
8. arrowHeadType markerEndId - markerStart / markerEnd
We improved the API for customizing the markers for an edge. With the new API you are able to set individual markers at the start and the end of an edge as well as customizing them with colors, strokeWidth etc. You still have the ability to set a markerEndId but instead of using different properties, the markerStart
and markerEnd
property accepts either a string (id for the svg marker that you need to define yourself) or a configuration object for using the built in arrowclosed or arrow markers.
Old API
const markerEdge = { source: '1', target: '2', arrowHeadType: 'arrow' };
New API
const markerEdge = {
source: '1',
target: '2',
markerStart: 'myCustomSvgMarker',
markerEnd: { type: 'arrow', color: '#f00' },
};
9. ArrowHeadType - MarkerType
This is just a wording change for making the marker API more consistent. As we are now able to set markers for the start of the edge, the name type ArrowHeadType has been renamed to MarkerType. In the future, this can also not only contain arrow shapes but others like circles, diamonds etc.
10. Attribution
This is not really a breaking change to the API but a little change in the general appearance of React Flow. We added a tiny "React Flow" attribution to the bottom right (the position is configurable via the attributionPosition
prop). This change comes with the new "React Flow Pro" subscription model. If you want to remove the attribution in a commercial application, please subscribe to "React Flow Pro" (opens in a new tab).