John Betancur
John Betancur
BlogAbout
Optimizing React Rendering

Optimizing React Rendering

August 18, 2019

Why does my component re-render when using React.memo or React.PureComponent?

TL;DR

Let’s start with the answer. Make sure any object based props you are passing to your component are not being recreated on every re-render cycle, otherwise, the point of these optimizations are defeated. Of course, you’re probably here because you want to dig a little deeper…

Houston, We Have a Problem

Such an issue arose while I was developing React Data Table. React Data Table has a deep component tree that consists of columns, rows, cells and in some places expensive calculations such as sorting, managing multiple checkbox state, column generation, etc.). Despite the use of React.memo on certain expensive components, the entire React Data Table library would re-render unnecessarily causing it to be noticeably slower when there were more than say twenty or thirty rows. The simple math is that a ten column table with thirty rows is three hundred something components (cells) all re-rendering.

Equality & Sameness

Before we delve into the solution, let’s briefly revisit how Javascript determines equality, starting with primitives. Primitives are strings, numbers, booleans, undefined and null (yep, undefined and null are types). These are considered value based comparisons. Comparing a primitive value to another equal primitive value will result in a true condition:

'hello' === 'hello' // true
42 === 42 // true
true === true // true
undefined === undefined // true
null === null // true

Object equality, however, is determined by reference. A reference is pointer to the location of the object in memory, not its value or contents:

Note that in Javascript arrays and functions are objects too!

{} === {} // false
[] === [] // false
(() => {}) === (() => {}) // false

The result is false because we’re comparing two objects that have different references. Let’s compare the same object reference:

const obj = {}
obj === obj // true

const arr = []
arr === arr // true

const func = () => {}
func === func // true

Comparing the same reference is true because obj refers to the same address in memory. One last thing regarding object equality. What if our object is derived from the result of a function?

function hello() { return 'hello planet earth' }
hello() === hello() // true - the return "value" is a string

function hello() { return { planet: 'earth' } }
hello() === hello() // false - the object reference is different

The same principles of equality apply.

Checkout Equality comparisons and sameness if you want a deeper dive into Javascript equality.

Really, Really, Checking React props for Equality

This concept took me way too long to grasp (I’m a slow learner), but think of React components as functions (i.e. function Component(props) {...}) that are called when the component first mounts (the function is first called), then subsequently every time a state change originates from a parent or within the component itself. But what if we wanted to skip subsequent function invocations (i.e. re-renders)? Why should the function run again if its props haven’t changed? Shouldn’t the result be the same anyway?

Pretend that ExpensiveChild is some crazy expensive component that slows down our UI. Luckily, React provides us with React.memo and React.PureComponent. Both give us an escape hatch to the rendering process and are a good starting point by allowing React to perform a shallow prop check on any props that are passed to a component before the component re-renders.

In the example below, you’ll notice that ExpensiveChild will re-render every time you click either of the buttons. You may already know that this is because setCount (this.setState in a class component) is a request for React to re-render Parent with the updated count. By design, when Parent re-renders React will also re-render all of its children all the way down the component hierarchy.

Let’s apply React.memo and React.PureComponent to ExpensiveChild and see what happens:

Using React.PureComponent

By extending our class Component with React.PureComponent React will only render ExpensiveChild on the initial mount. Thereafter, re-renders are skipped unless the string prop text changes:

Using React.memo

We can achieve the same result as above for a functional component by wrapping it with React.memo:

Wow, that was easy! No more re-renders!

Let’s make this more interesting by also passing the data object. For brevity and cool points, we are going to stick with the Hooks example:

Damn, damn, damn. Now we’re re-rendering again! Shouldn’t React.memo have taken care of this for us? What do you think is wrong here?

You guessed it! The data object is being re-created every time Parent re-renders. Which means the React.memo shallow prop checking in ExpensiveChild is skipped. What a waste!

One glaringly obvious solution is to just move data outside of Parent so it’s only created once, however, what if your data object needs access to the Parent scope?

There must be a way to cache a reference to data while keeping it’s scope within our component, then, pass that to ExpensiveChild instead of re-creating data on every re-render…

Memoization

Memoization is a fancy computer science term for caching the result of a value or function and keeping it’s object reference rather than creating a new one.

Let’s memoize the data prop before it’s passed to ExpensiveChild. In our functional component example we can use React.useMemo (React.useCallback is useful when we want to memoize a function):

Problem solved! Our ExpensiveChild component not longer re-renders unless data changes. By the way, you may have noticed that React.memo reads a lot like React.memoize. That’s because React.memo and even React.PureComponent are also memoization.


John Betancur

Written by John Betancur who lives and works in New York City building useful things. You should follow him on Twitter