React internally already optimizes the performance quite a bit without having to explicitly optimize for performance. React.memo can help you to optimize the number of renders of your React components even further.
In this article I will explain to you how you can optimize React performance by using React.memo, some common pitfalls you could encounter and why you shouldn't always use React.memo.
What is React.memo?
React.memo is a function that you can use to optimize the render performance of pure function components and hooks. It has been introduced in React v16.6..
Memo derives from memoization. It means that the result of the function wrapped in React.memo is saved in memory and returns the cached result if it's being called with the same input again.
Since it's used for pure functions: If the arguments don't change, the result doesn't change either. React.memo prevents functions from being executed in those cases.
I've written a small example to visualize this a little bit better. Go ahead and write something into the input field. You can also play around with the code on codepen.
Paints
0
Paints
0
Paints
0
The blue component contains three sub-components. 1 input field and 2 black tiles. In yellow, you can see the number of paints of each component.
If you typed something, you have seen that the left tile and the blue tile have updated with every keystroke, while the number of paints of the right tile didn't change.
The right tile is wrapped in a React.memo function which prevents the function from re-rendering when the props don't change.
I explore how re-rendering in React works and other ways of improving rendering performance in this article.
Now we will see how this works in detail.
How to use it
The basic usage looks like the following, you have to wrap your function component in a React.memo()
function.
This tends to look better with inline-style functions:
const Tile = React.memo(() => { let eventUpdates = React.useRef(0);
return (
<div className="black-tile">
<Updates updates={eventUpdates.current++} />
</div>
);
});
This little change will already help you improve the rendering performance of your components but there are some other issues you might face when dealing with more complex components.
Get notified about new tutorials
Join over 1,000 developers who receive React and JavaScript tutorials via email.
No spam. Unsubscribe at any time.
Common pitfalls
React.memo isn't working as expected?
Here are some common pitfalls you might encounter when starting off with React.memo:
Dealing with objects
Let's say we also pass an object property down to the Tile
component:
const App = () => {
const updates = React.useRef(0);
const [text, setText] = React.useState('');
const data = { test: 'data' };
return (
<div className="app">
<div className="blue-wrapper">
<input
value={text}
placeholder="Write something"
onChange={(e) => setText(e.target.value)}
/>
<Updates updates={updates.current++} />
<Tile />
<TileMemo data={data} /> </div>
</div>
);
};
Suddenly, React.memo seems to stop working and the memoized component renders on every keystroke again. If you don't believe me, try it out on codepen.
The reason React.memo doesn't work anymore is because it only does a shallow comparison of the
component's properties. The data
variable is being re-declared on every update of App
. This leads
to the objects not actually being the same because they have different references.
Solving this with areEqual
React.memo provides a solution for this in its second parameter. This parameter accepts a second areEqual
function,
which we can use to control when the component should update.
const TileMemo = React.memo(() => {
let updates = React.useRef(0);
return (
<div className="black-tile">
<Updates updates={updates.current++} />
</div>
);
}, (prevProps, nextProps) => { if (prevProps.data.test === nextProps.data.test) { return true; // props are equal } return false; // props are not equal -> update the component});
React.useMemo
As an alternative to this, you can wrap the object in React.useMemo()
, which will memoize the variable and
not create a new object.
const data = React.useMemo(() => ({
test: 'data',
}), []);
The second parameter of useMemo
is an array with the dependencies of the variable. If one of them changes,
React will recompute the value.
In our case, this won't happen since the array is empty.
Dealing with functions
In JavaScript, functions behave just like objects, which leads to the same problem we had before.
The onClick
function is being declared each time App
updates. TileMemo
then thinks that onClick
changed because
the reference has changed.
const App = () => {
const updates = React.useRef(0);
const [text, setText] = React.useState('');
const onClick = () => { console.log('click'); };
return (
<div className="app">
<div className="blue-wrapper">
<input
value={text}
placeholder="Write something"
onChange={(e) => setText(e.target.value)}
/>
<Updates updates={updates.current++} />
<Tile />
<TileMemo onClick={onClick} /> </div>
</div>
);
};
React.useCallback
We can solve this just like we did with objects, by memoizing the function.
const onClick = React.useCallback(() => {
console.log('click');
}, []);
Here the second parameter is again an array with dependencies which will trigger computation of the value if they change.
React.memo still doesn't work?
You encountered another issue with React.memo that isn't listed here?
Send me an email and I'll try to help you!
Why not use React.memo by default?
You could think that React.memo doesn't have any downsides and that you should wrap all your function components. The problem with that is that React.memo explicitly caches the function, which means that it stores the result (VDOM) in memory.
If you do this with too many or too big components this leads to more memory consumption. That's why you should be careful when memoizing large components.
The other case where you should avoid using it is when the component's props change frequently. React.memo introduces additional overhead which compares the props with the memoized ones. This might not affect performance too much but you certainly miss out on the performance optimization React.memo is made for.
React is already very efficient at optimizing rendering performance. Most of the time you shouldn't bother spending time on optimizing unncessary re-renders. The first step should always be to measure and identify performance bottlenecks. It's a good idea to profile your React app beforehand to see which components render the most. Applying React.memo to those components will have the biggest impact.
React.memo is only part of a bigger picture that involves understanding how React decides to re-render components. I explain how this works in another article, where I also explain why you probably don't need to use React.memo in the first place (I explain this under "How to optimize re-renders").
To get my latest content to your inbox, I recommend you subscribe to my email list and follow me on Twitter.