Let's begin by understanding what a render
is. A render is when React calls the render
function of a class component or calls a function component in order to get the React elements. Note that this is not actually making changes to the DOM. A component will re-render (subsequent renders after the initial render when the component is mounted) when:
- Internal state changes. e.g useState is used to update state.
- Props provided by the parent component change.
- Its consuming context values and they change.
- Parent re-renders.
React.memo helps with preventing unnecessary re-renders when the parent re-renders. Let's have a look at an example to help us understand this. Below is a component that renders a user's profile. The component that renders the user's phone number is a separate component. Note the console.log
in the App component which is the parent component and the console.log
in the PhoneNumber component which is a child component.
import { useState } from 'react';
function PhoneNumber({ phoneNumber }: { phoneNumber: string }) {
console.log('Child render....');
return <div className="font-semibold text-2xl mt-8">{phoneNumber}</div>;
}
function App() {
const [name, setName] = useState('Jones');
const [phoneNumber, setPhoneNumber] = useState('+1-202-555-0164 ');
console.log('Parent render....');
return (
<section className="mt-5">
<div>
<label htmlFor="name" className="mr-2 text-lg">
Name:
</label>
<input
type="text"
id="name"
className="rounded"
value={name}
onChange={(evt) => setName(evt.target.value)}
/>
</div>
<PhoneNumber phoneNumber={phoneNumber} />
</section>
);
}
export default App;
The App
component has two state values, name
and phoneNumber
. It has a text field that we use to edit the name. The phone number is never edited. The phone number is rendered in the PhoneNumber
component.
When the App component is first mounted we get the following logs:
Of importance is logging with App.tsx
at the far end, highlighted in red. Ignore the duplicate log from Vite.
On editing the name, the state of the parent component changes so it re-renders. This will cause the child to re-render as well. We get the following logs:
Note the extra two logs, highlighted in orange. Though the phone number did not change, the PhoneNumber component still re-renders. This is an unnecessary re-render.
We can prevent this unnecessary re-render with React.memo
. React.memo is a performance optimization tool provided by React. React.memo is a Higher Order Component, so let's pass it the PhoneNumber component as a parameter.
const PhoneNumberC = React.memo(PhoneNumber);
This is the component that we mount in the App component. The code should now look like this:
import { useState } from 'react';
import * as React from 'react';
function PhoneNumber({ phoneNumber }: { phoneNumber: string }) {
console.log('Child render....');
return <div className="font-semibold text-2xl mt-8">{phoneNumber}</div>;
}
const PhoneNumberC = React.memo(PhoneNumber);
function App() {
const [name, setName] = useState('Jones');
const [phoneNumber, setPhoneNumber] = useState('+1-202-555-0164 ');
console.log('Parent render....');
return (
<section className="mt-5">
<div>
<label htmlFor="name" className="mr-2 text-lg">
Name:
</label>
<input
type="text"
id="name"
className="rounded"
value={name}
onChange={(evt) => setName(evt.target.value)}
/>
</div>
<PhoneNumberC phoneNumber={phoneNumber} />
</section>
);
}
export default App;
When we edit the name we only get the parent re-render. Phone number component does not re-render:
We have prevented an unnecessary re-render. The phone number component in this case was not doing much but in a real-world app, a page could have hundreds of such components, and the re-renders do add up. The components could be doing some work during render for example formatting the phone number, preventing an unnecessary re-render in many such components will improve the efficiency of the app.