A deep dive into Pure Component and React.memo(), and why we need them

React renders components much more than you think. Let’s find out why.

--

A common mistake is to believe that a React component is smart enough to understand whether it needs to render itself again or not, based on its own props and state. But that’s not entirely the truth.

To better understand why, we should start by learning about the limitations of a non-pure React component by looking at two examples. After that, it will be easier to grasp the benefits of PureComponent and React.memo() by looking at two other examples.

A short summary of what’s coming:

  • Example 1 — Two Class Components
  • The Reconciliation
  • Example 2 — Two Function Components with Hooks
  • Example 3 — One Pure Class Component + One Class Component
  • Example 4 — One Pure Function Component + One Function Component
  • One more thing
  • Conclusions

Example 1 — Two Class Components

This code is pretty straightforward: when the Page is loaded, the value of user in the state object is an empty string. Only after the first click on the button, the value is set to “Jack”.

So what would you expect to be printed in the console, after clicking the button for the first time?
You will probably guess it correctly: since the state object gets updated, the render function will be called again, thus both console.log messages will be printed.

But what do you think would happen if you were to click the button a second time, third time, or more?
The answer to this is a little less trivial.

You might think that nothing will be rendered again, since it looks like the state object has not changed. Consequently nothing should be printed in the console.
After all, the documentation reminds us that React re-renders a component only if its props or state have changed.
Yet, this is not what happens. In our example, each component will be re-rendered every time you click on the button, and both messages will be printed in the console over and over again.

Remember: in React, props and state are both immutable objects.
This means firstly that state object in Page is in fact changing. Rather, it’s being re-created by the setState execution.
Secondly, due to Page being re-rendered, WelcomeMessage will be called again with a new instance of its props object, which is a whole new object.

Hint — if you are not familiar with this concept, try to paste {} === {} in the console and check the result. Also you can check out this article.

In short, in our example, all components will be rendered over and over again, even when it’s not necessary.

Do you see the limitations of a React component now?

Before we move to the next example it’s important to specify what exactly React does on every render.

The Reconciliation

In React, to render means that the render() method is called, but the method itself doesn’t update the DOM directly. Instead, React starts a process called reconciliation in which it is able to understand what parts of the DOM should be updated. It does so by comparing the previous DOM with the next one.

The reconciliation process uses a technique called Virtual DOM which is blazingly fast, so usually we don’t have to worry about it and our components just work as expected. However, when a lot of unnecessary rendering is happening, the reconciliation might slow down your application.

To avoid unnecessary renders, thus reconciliation, we can use PureComponent (with class components) or React.memo() (with function components).

We will see them in action soon, but first let’s look at the following.

Example 2 — Two Function Components with Hooks

This example is adopting same logic as the Example 1, but using function components and hooks.

Let’s jump directly to the behaviour of the application when clicking the button for a second time or more.
You could expect the outcome to be the same as the one in the class component of the previous example. But it’s not. Why?

Here the state of the component, which is user, is not an object anymore, but a simple string instead.

Remember how React was comparing two objects earlier?
In this case the comparison is between two strings, which happen to have the same value “Jack”. The equality check used by React will then return true, and so no render will happen. Boom!

And, since Page is not rendered, WelcomeMessage is not called and not rendered again.

Here we can discover one of the advantages of using Hooks 😎

Please note: on the second click you will actually see a “render Page” log, but this behaviour will not happen again from the third click on. As stated in the official documentation, React may still need to render the component (where the hook is being used) one last time.

Now, what if the type of user needs to be an object instead of a string?

Well, in that case the behaviour will unfortunately be exactly the same as the one in the class component.

The next two examples will cover pure components and how they solve the limitations we have just seen.

Example 3 — One Pure Class Component + One Class Component

The following code is exactly the same as the one in the first example, except for the Page component which extends React.PureComponent instead of React.Component.

Again, how do you expect the application to behave upon clicking the button for a second time or more?

The behaviour is finally what we were expecting to see from the very beginning: the second click on the button doesn’t produce any log and therefore no render happens.

So how exactly does PureComponent work?

In PureComponent every time props or state changes, React performs a shallow compare between props and nextProps objects, and also between state and nextState objects.
A shallow compare is a comparison between every key at the first level in the objects.

In our scenario, after having clicked the button for a second time, the pure component Page will perform the shallow compare on the state and, since it’s not changed, the component will not be rendered again. Consequently, the WelcomeMessage component won’t either.

The last example is similar, but I would like to focus on a couple of interesting things.

Example 4 — One Pure Function Component + One Function Component

The following code is similar to Example 2 except that:

  • The WelcomeMessage function component is now wrapped around React.memo()
  • The user value is not a string but an object

First things first: React.memo() does the same as PureComponent but it does not check for changes in state, only in props.

It’s important to notice that the pure component in the third example was Page, and now it’s WelcomeMessage.

So, one last time, what do you expect to see after a second click on the button?

The answer is only the log message “render Page”.

I guess you already know the reason but, to be sure:

  • user is now an object. It will cause the re-render of Page on every call of setUser.
  • WelcomeMessage is using React.memo() instead. Every time the component is rendered by the parent, a shallow comparison will be performed. All the props, thus just the string name, will be compared in order to decide whether it’s necessary to render the component or not.

One more thing

In some circumstances the shallow comparison performed by React is not enough for your purpose. Imagine, for example, a deeply nested object.

In these cases, you can do the following:

For PureComponent there is an API method called shouldComponentUpdate. You can use it to write your own compare logic. Your logic should then return true if the component has to be updated, false if it does not.

For the function component, React.memo(Component, areEqual) accepts as a second argument a compare function areEqual. It should return true if the component doesn’t have to be updated, false if it does.

Conclusions

Use pure components responsibly. The shallow comparison does not come for free. It’s an expensive computation and it’s better to avoid it if not necessary.

Don’t convert every component to pure, it could end up in worse performance.
Start instead by trying to convert a component to pure if it renders a lot of unnecessary times.

If you are not familiar with pure components, the advice is to only rely on them if you face some performance issues.

Thanks for reading and happy coding!

Thanks to Alessio Van Keulen for proofreading the article.

--

--

Davide Cantelli
Just Eat Takeaway-tech

Software developer, passionate photographer, design lover and tireless dreamer