React re-rendering, PureComponents and memoization

Hugo Weber
OVRSEA
Published in
3 min readAug 16, 2018

In React, re-rendering Components can cause performance issues, especially when dealing with deep Component trees. We will see what can cause wasteful renders and how to avoid them.

The rendering cycle

Here are the main events that leads to a Component update :

  • A change of the Component props or state
  • Another Component updating higher in the tree

Both of those things will trigger the shouldComponentUpdate() method, which decides if the rendering lifecycle should proceed.

By default shouldComponentUpdatereturns true, causing React to update its virtual DOM. We can do a first optimization here by using PureComponent in children directly under components subject to a lot of renders, typically containers.

Optimizations

PureComponents

PureComponent includes by default a shallow comparison of props and state in shouldComponentUpdate, meaning it won’t re-render with the same props and state.

However, the shallow compare will fail and still trigger an update if a prop is dynamically generated:

unnamed object / functions

In the example above, even if the 3 props passed are not used and hold the same values, each will fail the shallow compare as they will be generated every time Container re-renders: list.filter returns a new Array, and the unnamed inline function and object will have new references.

Extracting elements

The last two can be optimized by naming and extracting them:

We are left here with only one unnecessary render caused by filteredList.

We could revert the Child to a simple Component and do a deep equality check in shouldComponentUpdate, but it is a bad idea overall as comparing nested structures can be very costly.

Luckily there’s an awesome tool for that: data memoization !

Memoizing your data

Memoization is a powerful tool that acts as a cache: it takes a pure function as a parameter and is guaranteed to return the result of the last invocation for the same input. In other words, with the same input it will return the memorized result of the last time you called it.

The performance benefits can be huge: it avoids computing costly operations over and over, and in our use case it passes the shallow equality prop comparison as our list Array will have the same reference!

There is quite a few modules that helps you memoize your data, and we chose memoize-one as it is lightweight, fast and simple to use.

Here is the same Container with memoization:

Voila ! Now the Child Component re-renders only when the Container list prop changes, as expected.

Disclaimer: your function has to be pure! The example below will not work:

this.filterList() will always return the same memoized result here even if this.props.list changes because it will always have the same (empty) parameter.

Summing it up

You can avoid a lot of wasteful renders by combining simple optimizations :

  • using PureComponent in direct children of your container
  • extracting the inline function props as methods
  • extracting your objects
  • memoizing nested data in your container

Our use case at OVRSEA was in the reporting tab of our app, where one of our container Component could re-render between 5 and 10 times on the first mount and 2 times when updated, with every child below it.

It was not only slow but also made our charts buggy: triggering a re-render in the middle of an animation led the graph to flash and not having any label.

In action, this graph before and after optimizations with Update Highlights turned on in React DevTools :

using recharts graph API

Hope this helps ! Further reading :

https://reactjs.org/docs/reconciliation.html

https://marmelab.com/blog/2017/02/06/react-is-slow-react-is-fast.html

https://spin.atomicobject.com/2017/10/24/react-reselect/

--

--