DEV Community

Cover image for React Flow(xyFlow) Optimization
Usman Abdur Rehman
Usman Abdur Rehman

Posted on

React Flow(xyFlow) Optimization

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>
Enter fullscreen mode Exit fullscreen mode

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>
  );
});
Enter fullscreen mode Exit fullscreen mode

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

  1. Moving all nondependent Functions/Data outside the component
  2. Wrapping all Functions/Data dependent on props/state of the component with useCallback/useMemo
  3. Wrapping all children with React.memo where applicable
  4. 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)