Leonard Kioi Kinyanjui
Learn & Build

Learn & Build

Reducing unnecessary re-renders with React.memo

Photo by Marc-Olivier Jodoin on Unsplash

Reducing unnecessary re-renders with React.memo

Leonard Kioi Kinyanjui's photo
Leonard Kioi Kinyanjui
·Jul 16, 2022·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

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.

Screenshot 2022-07-16 at 11.13.50.png

When the App component is first mounted we get the following logs:

Screenshot 2022-07-16 at 12.17.10.png

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:

Screenshot 2022-07-16 at 12.39.00.png Screenshot 2022-07-16 at 12.24.43.png

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:

Screenshot 2022-07-16 at 12.17.41.png 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.

 
Share this