Node movements can feel janky in React Flow if you have a lot of custom nodes or unoptimized code and this messes up the UX of the flow. In this blog I will talk about some reasons why React Flow can behave slowly and how you could optimize it.
Memoization
The nodes state gets updated every time you do something with the node. So if you are moving a node, the state would get updated multiple times.
If you have some JSX in that component, in which the state is present e.g some node buttons being mapped in a sidebar which would be dropped on the flow to create custom nodes, then you will see that the node movement would not be smooth.
<Box>
<Text fontSize={"xs"} fontWeight={"bold"}>
Messages
</Text>
<Grid templateColumns="repeat(2, 1fr)" gap={2} mt={1}>
{MESSAGES_NODES.map(({ id, icon, label }) => (
<DragNode
label={label}
icon={icon}
onDragStart={(e) => onDragStart(e, id)}
/>
))}
</Grid>
<Text fontSize={"xs"} mt={3} fontWeight={"bold"}>
Choices
</Text>
<Grid templateColumns="repeat(2, 1fr)" gap={2} mt={1}>
{CHOICE_NODES.map(({ id, icon, label }) => (
<DragNode
label={label}
icon={icon}
onDragStart={(e) => onDragStart(e, id)}
/>
))}
</Grid>
</Box>
To ensure smooth node movement, you can port that mapping logic into a separate memoized component. You can also wrap the onDragStart
function in a useCallback
. This would make sure that the button mapping logic only runs once and is not at all bothered by the nodes state updating.
const NodesSidebar = React.memo(function NodesSidebar({
onDragStart,
}: {
onDragStart: (e: React.DragEvent<HTMLDivElement>, id: NodeType) => void;
}) {
return (
<Box>
<Text fontSize={"xs"} fontWeight={"bold"}>
Messages
</Text>
<Grid templateColumns="repeat(2, 1fr)" gap={2} mt={1}>
{MESSAGES_NODES.map(({ id, icon, label }) => (
<DragNode
label={label}
icon={icon}
onDragStart={(e) => onDragStart(e, id)}
/>
))}
</Grid>
<Text fontSize={"xs"} mt={3} fontWeight={"bold"}>
Choices
</Text>
<Grid templateColumns="repeat(2, 1fr)" gap={2} mt={1}>
{CHOICE_NODES.map(({ id, icon, label }) => (
<DragNode
label={label}
icon={icon}
onDragStart={(e) => onDragStart(e, id)}
/>
))}
</Grid>
</Box>
);
});
Optimizing Custom Nodes
If the node movement is feeling janky then there is a high chance it is happening because of your custom nodes.
You can find that out by early returning a div
with some text in your custom node. If the performance is improved, then it is because of your custom node.
Like any React component, you should try to memoize this custom node component by
- Moving all nondependent Functions/Data outside the component
- Wrapping all Functions/Data dependent on props/state of the component with
useCallback/useMemo
- Wrapping all children with
React.memo
where applicable - Not mounting children components which are not needed etc.
Lazily Rendering Nodes
By default all nodes, are rendered on the flow even if they are not visible in the viewport. This can be an issue if you have a large number of nodes
One thing you can do is lazily render nodes based on if the nodes are present in the viewport or not. You can do that by setting the
onlyRenderVisibleElements
prop of ReactFlow
to false
.
Snapping Nodes to Grid
Normally when you are moving nodes, for every pixel you move approximately, a state update occurs to update the node position.
However, you can enable the snapping to grid feature to make the node move in a grid of some pixels which you will specify. e.g. if you set the snapToGrid
prop to true
and snapGrid
prop to [50, 50]
, then the node would move in 50x50px segments, and the node state would update less frequently causing an increase in performance.
Collapsing/Expanding Node Trees
If you have a nodes structure in the form of a tree, you can collapse or expand nodes to render only a subset of the actual nodes. By default, you can only render the nodes which are important and key, and not render all the nodes which are not that important as collapsible/expandable trees which can be expanded based on the usage.
Conclusion
React Flow is an awesome library which can be used to create Robust Node Based Interfaces and can be a great asset to have in your Frontend Toolbelt but it is vital that you provide the best UX in your React Flow based product in terms of performance.
I hope this article shed some light on how you can optimize your React Flow project. Cheers 👋
Top comments (0)