React — When Should Pure Components Be Used?
React.memo / React.PureComponent usage guide
What’s a Pure Component?
(Official docs: React.PureComponent, React.memo)
In the following example, when App
re-renders,<Main userName={userName}/>
and <Header/>
are called:
And they always return new React Elements.
Remember that JSX
is just syntax sugar for React.createElement
(in React 17 it’s equivalent mostly conceptually) so App
‘s code is equivalent to:
Usually, as a result of creating React Elements, they also re-render, so their code will run.
If they are pure, however, they only re-render when any of their props change, otherwise React re-uses the last rendered result.
In our case, a pure Header
would never re-render because it has no props.
A pure Main
will only re-render if userName
changes:prevProp.userName !== nextProp.userName
So when userName
doesn’t change, Main
’s last result is re-used and its code, including heavyCalculation
doesn’t run.
Sandbox
In the following sandbox, I used an awesome library by a friend of mine, Nir Avraham: use-header-scroll.
The library’s hook returns different header sizes as you scroll resulting in a neat effect where the scroll hides and reveals itself as you scroll.
Try scrolling when Main
is pure and isn’t pure. It can be changed using the checkbox in the middle of the header.
Scroll Performance
In the sandbox above, you probably noticed how scrolling was laggy when Main
was not pure.
Profiler For The Rescue!
When you encounter a React performance issue, try recording the scenario with the React Profiler. In our case:
- Make sure
Main
is not pure in order to prepare the performance issue scenario - Start recording
- Scroll (reproduce the performance issue)
- Stop the recording
After that, your profiler should look like this:
Now we can see that the issue is that Main
is being called on every single scroll.
We can also see a similar issue using Chrome Performance Profiler:
The performance issue happens because React has to run the relatively huge block of code in Main
on every single scroll event before it could paint the updated App
results on the screen (Main
and Header
).
The time difference between scrolling events and Header
’s height changes paintings on the screen makes it feel laggy.
Preventing The Heavy Render
When Main
is pure, however, it is rendered only once, and React doesn’t need to run its relatively heavy code on each scroll event.
Then Why Not Make All React Components Pure?
For more details see the following GitHub issue on Facebook’s repo:
Q: When should you NOT use React memo?
In short, it’s because React.memo has a cost in terms of memory and CPU because it has to shallowly compare previous props to the next props.
This sounds like not a big deal, but it is for bigger applications and considered a deal big enough to decide manually what has to be pure and what isn’t.
It seems like, in most cases, making React components pure will be redundant, because they will always re-render anyway.
For example, in the following cases, each of the props: onClick
, style
, children
, and items
are generated inline and will be a new prop on each render:
So if Main always re-renders, why would you compare its props on each render?
Optimizations To Help Keeping Components Pure
In all the cases mentioned in the example above, you can memoize props using useCallback
, useMemo
, or techniques like extracting prop definitions out of the component, and more.
But these optimizations too, have their own costs in terms of resources and code readability.
For example, this is how App
from the example above might look like to make sure Main
won’t always re-render (unless one of the props changes):
As you can see the code is more complicated, less readable, more prone to bugs, and costs resources to memoize in of itself (the hooks in lines 14–16 cost resources).
Does it worth it? You tell me. (After using the profiler 😉)
So, When Should Pure Components in React be Used Indeed?
Let’s narrow down the considerations in hand to create a rule of thumb:
Considerations:
- Making a component pure, forces React to compare props before re-rendering the component. (
prevProp !== newProp
) - A React Component, especially a bigger one, is relatively expensive to render.
- A component with inline generated props will always re-render (like
style={{width: 100%}}
. - Optimizations to inline generated props to make sure a component doesn’t always re-render has its overheads (code readability, resources).
Rule of thumb:
A component should be made pure if its render causes a performance issue and the render can be prevented by making the component pure.
Good candidates for components that cause performance issues which can be solved by making these components pure are usually:
- Not small. (has considerable calculations or generates many react elements inside)
- Re-render on user interaction. (click, scroll, hover)