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 aroundReact.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 ofPage
on every call ofsetUser
.WelcomeMessage
is usingReact.memo()
instead. Every time the component is rendered by the parent, a shallow comparison will be performed. All theprops
, thus just the stringname
, 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.