DEV Community

Discussion on: Optimizing Re-Renders Using React.memo()

Collapse
 
edriso profile image
Mohamed Idris

MIND GRENADE!!! 💣

Imagine we need to add a removePerson method to remove a person from a list. So, let’s start with the basic setup in App.jsx:

App.jsx

import { useState } from 'react';
import { data } from '../../../../data';
import List from './List';

const App = () => {
  const [people, setPeople] = useState(data);
  const [count, setCount] = useState(0);

  const removePerson = (id) => {
    const newPeople = people.filter((person) => person.id !== id);
    setPeople(newPeople);
  };

  return (
    <section>
      <button
        className="btn"
        onClick={() => setCount(count + 1)}
        style={{ marginBottom: '1rem' }}
      >
        count {count}
      </button>
      <List people={people} removePerson={removePerson} />
    </section>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In this code, we’re removing a person by filtering the people array, and passing the removePerson function down to the List component.

List.jsx (Memoized Component)

import { memo } from 'react';
import Person from './Person';

const List = ({ people, removePerson }) => {
  return (
    <div>
      {people.map((person) => {
        return (
          <Person
            key={person.id}
            {...person}
            removePerson={() => removePerson(person.id)}
          />
        );
      })}
    </div>
  );
};
export default memo(List);
Enter fullscreen mode Exit fullscreen mode

We use memo here to memoize the List component, so it only re-renders if the people or removePerson props change.

Person.jsx (Displaying Each Person)

const Person = ({ name, removePerson }) => {
  return (
    <div>
      <h4 onClick={removePerson}>{name}</h4>
    </div>
  );
};
export default Person;
Enter fullscreen mode Exit fullscreen mode

What’s the Problem?

Now, everything works fine when we remove a person—List re-renders because the people array changes. But what happens when we click the count button? 🤔

The Unexpected Re-render!

When we click on the count button, the removePerson function is re-created on each render. Because the function reference changes, it’s considered a new value by React. So, even though the people array hasn’t changed, the removePerson function prop is treated as "changed," causing the List component to re-render unnecessarily.

The Memoization Breakdown

In the List component, we used memo, which only prevents re-renders if the props change. However, the function passed as a prop (removePerson) is created from scratch every time the App component re-renders (because of the setCount state change). This means that React sees the removePerson function as "changed" every time the App re-renders.

Here’s what happens:

  • Expected behavior: List should not re-render if only the count changes and the people array stays the same.
  • Actual behavior: List does re-render because the removePerson function is recreated every time the App component re-renders.

DevTools Insight

We can see this in the React DevTools Profiler. Even though the people array hasn’t changed, we can clearly see that the removePerson function is treated as a prop change, triggering a re-render of the List component.

Profiler Example 1.1

Profiler Example 1.2

Here’s the breakdown:

  • The removePerson function is created from scratch on every render, and React treats it as a new prop.
  • This causes List to re-render, even though the people prop hasn’t changed.

Why Is This Happening?

  • memo is working fine for checking props, but it doesn’t handle functions that are recreated on each render.
  • React sees the function as a new value every time, causing unnecessary re-renders.

The Solution: useCallback

Next, we’ll introduce the useCallback hook, which will help us preserve the function reference across renders, preventing unnecessary re-renders in this scenario.

With useCallback, we can ensure that removePerson only gets recreated when the dependencies (like setPeople) change, not every time the component re-renders.

Stay tuned for the fix! 🔧


TL;DR:

  • Memoization works for props, but it doesn't prevent re-renders caused by function references changing.
  • Using useCallback will help us preserve the function reference and avoid unnecessary re-renders when only other state (like count) changes.