Introduction to React Memo

If you are interested to learn about the React Router

memo is a higher order component. If your component renders the same result given the same props, you can wrap it in a call to React. memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result. Using memo will cause React to skip rendering a component if its props have not changed. This can improve performance. This section uses React Hooks. See the React Hooks section for more information on Hooks.

When would you use a memo?

Use a memo when you are writing a message built to last.

If your communication is a detailed proposal, a significant report, a serious recommendation, a technical explanation, meeting minutes, a new policy, or something else that readers will consult more than once, make it a memo.

Problem

In this example, the Todos component re-renders even when the todos have not changed.

Example:

index.js:

import { useState } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState(["todo 1", "todo 2"]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  return (
    <>
      <Todos todos={todos} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Todos.js:

const Todos = ({ todos }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
    </>
  );
};

export default Todos;

When you click the increment button, the Todos component re-renders. If this component was complex, it could cause performance issues.

Solution

To fix this, we can use memo. Use memoto keep the Todos component from needlessly re-rendering. Wrap the Todos component export in memo:

Example:

index.js:

import { useState } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState(["todo 1", "todo 2"]);

  const increment = () => {
    setCount((c) => c + 1);
  };

  return (
    <>
      <Todos todos={todos} />
      <hr />
      <div>
        Count: {count}
        <button onClick={increment}>+</button>
      </div>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Todos.js:

import { memo } from "react";

const Todos = ({ todos }) => {
  console.log("child render");
  return (
    <>
      <h2>My Todos</h2>
      {todos.map((todo, index) => {
        return <p key={index}>{todo}</p>;
      })}
    </>
  );
};

export default memo(Todos);

How to use React memo

React’s memo API can be used to optimize the rendering behavior of your React function components. We will go through an example component to illustrate the problem first, and then solve it with React’s memo API. Keep in mind that most of the performance optimizations in React are premature. React is fast by default, so every performance optimization is opt-in in case something starts to feel slow.

Note: If your React component is still rendering with React memo, check out this guide about React’s useCallback Hook. Often a re-rendering is associated with a callback handler which changes for every render.

Note: Don’t mistake React’s memo API with React’s useMemo Hook. While React memo is used to wrap React components to prevent re-renderings, useMemo is used to memoize values.

Let’s take the following example of a React application which renders a list of user items and allows us to add users to the list. We are using React’s useState Hook to make this list stateful:

import React from 'react';
import { v4 as uuidv4 } from 'uuid';

const App = () => {
  const [users, setUsers] = React.useState([
    { id: 'a', name: 'Robin' },
    { id: 'b', name: 'Dennis' },
  ]);

  const [text, setText] = React.useState('');

  const handleText = (event) => {
    setText(event.target.value);
  };

  const handleAddUser = () => {
    setUsers(users.concat({ id: uuidv4(), name: text }));
  };

  return (
    <div>
      <input type="text" value={text} onChange={handleText} />
      <button type="button" onClick={handleAddUser}>
        Add User
      </button>

      <List list={users} />
    </div>
  );
};

const List = ({ list }) => {
  return (
    <ul>
      {list.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

const ListItem = ({ item }) => {
  return <li>{item.name}</li>;
};

export default App;

If you include a console.log statement in the function body of the App, List, and ListItem components, you will see that these logging statements run every time someone types into the input field:

const App = () => {
  console.log('Render: App');

  ...
};

const List = ({ list }) => {
  console.log('Render: List');
  return (
    <ul>
      {list.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
};

const ListItem = ({ item }) => {
  console.log('Render: ListItem');
  return <li>{item.name}</li>;
};

After typing into the input field, all the components re-render because the App component updates its state and all of its child components will re-render by default.

// after typing one character into the input field

Render: App
Render: List
Render: ListItem
Render: ListItem

That’s the default behavior given by React and most of the time it’s fine to keep it this way as long as your application doesn’t start to feel slow.

But once it starts to feel slow, such as rendering a huge list of items every time a user types into the input field, you can use React’s memo API to memoize your component’s function:

const List = React.memo(({ list }) => {
  console.log('Render: List');
  return (
    <ul>
      {list.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
});

const ListItem = ({ item }) => {
  console.log('Render: ListItem');
  return <li>{item.name}</li>;
};

Now when we type into the input field, only the App component re-renders because it’s the only component affected by the changed state. The List component receives its memoized props from before, which haven’t changed, and thus doesn’t re-render at all. The ListItem follows suit without using React’s memo API because the List component already prevents the re-render.

// after typing one character into the input field

Render: App

That’s React’s memo function in a nutshell. It seems like we don’t need to memo the ListItem component. However, once you add an new item to the list with the button, you will see the following output with the current implementation:

// after adding an item to the list

Render: App
Render: List
Render: ListItem
Render: ListItem
Render: ListItem

By adding an item to the list, the list changes which causes the List component to update. For now that’s the desired behavior because we want to render all the items (2 items) plus the new item (1 item). But perhaps it would be more efficient to render only the one new item instead of all the items:

const List = React.memo(({ list }) => {
  console.log('Render: List');
  return (
    <ul>
      {list.map((item) => (
        <ListItem key={item.id} item={item} />
      ))}
    </ul>
  );
});

const ListItem = React.memo(({ item }) => {
  console.log('Render: ListItem');
  return <li>{item.name}</li>;
});

After trying the scenario from before, by adding an item to the list, with the new implementation with React’s memo function, you should see the following output:

// after adding an item to the list

Render: App
Render: List
Render: ListItem

Only the new item renders. All the previous items in the list remain the same and thus don’t re-render. Now only the components which are affected from the state changes rerender.

You might be wondering why you wouldn’t use React memo on all your components or why React memo isn’t the default for all React components in the first place. Internally React’s memo function has to compare the previous props with the new props to decide whether it should re-render the component. Often the computation for this comparison can be more expensive than just re-rendering the component.

In conclusion, React’s memo function shines when your React components become slow and you want to improve their performance. Often this happens in data heavy components, like huge lists where lots of components have to rerender once a single data point changes.

Introduction to React Memo
Show Buttons
Hide Buttons