React Rendering and Re-Rendering Breakdown With Vanilla Hooks

Ben Lu
The Startup
2 min readAug 15, 2020

--

Anyone who’s worked on react long enough will realize that the cost of rendering the virtual DOM on seemingly unrelated components adds up. So the question is, when do you need to optimize, and what’s a good style to start with?

Note: we will not be looking at real DOM costs, we are assuming React is smart about these, the best optimization will probably be time slicing.

All details were worked out by playing around in a dummy create react app repo here: https://github.com/ayroblu/render-count (warning not pretty)

What we know

  1. The virtual DOM is a tree, this means that re-renders will cause all naive child nodes to re-render, but not their sibling and parent nodes. This makes sense because we do data transfer in this way with props.
  2. children is just another prop, so nesting nodes is the same as passing them in props.
  3. context change will cause all nodes that use this context with useContext to change.

Trees and memoisation

So what we know from 1. is that any state orchestration in parent nodes will be expensive. One option here is to wrap all functions in React.memo, and of course make sure you memoise all props like objects and functions.

Children and reference props

What we know from 2. is that composition costs us render cycles, because JSX creates new objects which creates a new reference, which will trigger re-renders, even with React.memo. Note however that basic child components will not re-render in this case

Context

What we know from 3. is that useContext and more generally, global state becomes more expensive here. The ways to mitigate this are using multiple narrower context’s, and/or to adopt a redux pattern with “connect” style components.

This way, all the connected components will always re-render, but the child components will only change when (in this case) value1 changes. Of course you can write or use an actual connect higher order component for this, this code just outlines the principle.

Summary

  1. Use React.memo everywhere. As has been highlighted by other blog posts, this may not necessarily improve performance in all cases, however the downside is relatively low, and the upside could be relatively high, especially when you use fancy third party components. If you want to be optimal, test and measure the difference in speed, but this is more effort than it’s worth, it’s a more sensible default than using it nowhere.
  2. Beware objects, including composition of nodes. Memoise where reasonable here.
  3. If using a global state, consider using multiple context’s in lieu of the combineReducers redux pattern. Also consider using a connect pattern to stop unnecessary re-renders.

--

--