How to Implement Memoization in React to Improve Performance

Share this article

How to Implement Memoization in React to Improve Performance

In this tutorial, we’ll learn how to implement memoization in React. Memoization improves performance by storing the results of expensive function calls and returning those cached results when they’re needed again.

We’ll cover the following:

  • how React renders the UI
  • why there’s a need for React memoization
  • how we can implement memoization for functional and class components
  • things to keep in mind regarding memoization

This article assumes you have a basic understanding of class and functional components in React. If you’d like to brush up on those topics, check out the official React docs on components and props.

Memoization in React

How React Renders the UI

Before going into the details of memoization in React, let’s first have a look at how React renders the UI using a virtual DOM.

The regular DOM basically contains a set of nodes represented as a tree. Each node in the DOM is a representation of a UI element. Whenever there’s a state change in your application, the respective node for that UI element and all its children get updated in the DOM and then the UI is re-painted to reflect the updated changes.

Updating the nodes is faster with the help of efficient tree algorithms, but the re-painting is slow and can have a performance impact when that DOM has a large number of UI elements. Therefore, the virtual DOM was introduced in React.

This is a virtual representation of the real DOM. Now, whenever there’s any change in the application’s state, instead of directly updating the real DOM, React creates a new virtual DOM. React then compares this new virtual DOM with the previously created virtual DOM to find the differences that need to be repainted.

Using these differences, the virtual DOM will update the real DOM efficiently with the changes. This improves performance, because instead of simply updating the UI element and all its children, the virtual DOM will efficiently update only the necessary and minimal changes in the real DOM.

Why We Need Memoization in React

In the previous section, we saw how React efficiently performs DOM updates using a virtual DOM to improve performance. In this section, we’ll look at a use case that explains the need for memoization for further performance boost.

We’ll create a parent class that contains a button to increment a state variable called count. The parent component also has a call to a child component, passing a prop to it. We’ve also added console.log() statements in render the method of both the classes:

//Parent.js
class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("Parent render");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <Child name={"joe"} />
      </div>
    );
  }
}

export default Parent;

The complete code for this example is available on CodeSandbox.

We’ll create a Child class that accepts a prop passed by the parent component and displays it in the UI:

//Child.js
class Child extends React.Component {
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

Whenever we click the button in the parent component, the count value changes. Since this is a state change, the parent component’s render method is called.

The props passed to the child class remain the same for every parent re-render, so the child component should not re-render. Yet, when we run the above code and keep incrementing the count, we get the following output:

Parent render
Child render
Parent render
Child render
Parent render
Child render

You can increment the count for the above example yourself in the following sandbox and see the console for the output:

From this output, we can see that, when the parent component re-renders, it will also re-render the child component — even when the props passed to the child component are unchanged. This will cause the child’s virtual DOM to perform a difference check with the previous virtual DOM. Since we have no difference in the child component — as the props are the same for all re-renders — the real DOM isn’t updated.

We do have a performance benefit where the real DOM is not updated unnecessarily, but we can see here that, even when there was no actual change in the child component, the new virtual DOM was created and a difference check was performed. For small React components, this performance is negligible, but for large components, the performance impact is significant. To avoid this re-render and virtual DOM check, we use memoization.

Memoization in React

In the context of a React app, memoization is a technique where, whenever the parent component re-renders, the child component re-renders only if there’s a change in the props. If there’s no change in the props, it won’t execute the render method and will return the cached result. Since the render method isn’t executed, there won’t be a virtual DOM creation and difference checks — thus giving us a performance boost.

Now, let’s see how to implement memoization in class and functional React components to avoid this unnecessary re-render.

Implementing Memoization in a Class Component

To implement memoization in a class component, we’ll use React.PureComponent. React.PureComponent implements shouldComponentUpdate(), which does a shallow comparison on state and props and renders the React component only if there’s a change in the props or state.

Change the child component to the code shown below:

//Child.js
class Child extends React.PureComponent { // Here we change React.Component to React.PureComponent
  render() {
    console.log("Child render");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default Child;

The complete code for this example is shown in the following sandbox:

The parent component remains unchanged. Now, when we increment the count in parent component, the output in the console is as follows:

Parent render
Child render
Parent render
Parent render

For the first render, it calls both parent and child component’s render method.

For subsequent re-render on every increment, only the parent component’s render function is called. The child component isn’t re-rendered.

Implementing Memoization in a Functional Component

To implement memoization in functional React components, we’ll use React.memo().React.memo() is a higher order component (HOC) that does a similar job to PureComponent, avoiding unnecessary re-renders.

Below is the code for a functional component:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child); // Here we add HOC to the child component for memoization

We also convert the parent component to a functional component, as shown below:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };
  console.log("Parent render");
  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} />
    </div>
  );
}

The complete code for this example can be seen in the following sandbox:

Now, when we increment the count in the parent component, the following is output to the console:

Parent render
Child render
Parent render
Parent render
Parent render

The Problem with React.memo() for Function Props

In the above example, we saw that when we used the React.memo() HOC for the child component, the child component didn’t re-render, even if the parent component did.

A small caveat to be aware of, however, is that if we pass a function as prop to child component, even after using React.memo(), the child component will re-render. Let’s see an example of this.

We’ll change the parent component as shown below. Here, we’ve added a handler function that we’ll pass to the child component as props:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = () => {
    console.log("handler");    // This is the new handler that will be passed to the child
  };

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

The child component code remains as it is. We don’t use the function we’ve passed as props in the child component:

//Child.js
export function Child(props) {
  console.log("Child render");
  return (
    <div>
      <h2>{props.name}</h2>
    </div>
  );
}

export default React.memo(Child);

Now, when we increment the count in parent component, it re-renders and also re-renders the child component, even though there’s no change in the props passed.

So, what caused the child to re-render? The answer is that, every time the parent component re-renders, a new handler function is created and passed to the child. Now, since the handler function is recreated on every re-render, the child, on a shallow comparison of props, finds that the handler reference has changed and re-renders the child component.

In the next section, we’ll see how to fix this issue.

useCallback() to Avoid Further Re-rendering

The main issue that caused the child to re-render is the recreation of the handler function, which changed the reference passed to the child. So, we need to have a way to avoid this recreation. If the handler isn’t recreated, the reference to the handler won’t change — so the child won’t re-render.

To avoid recreating the function every time when the parent component is rendered, we’ll use a React hook called useCallback(). Hooks were introduced in React 16. To learn more about hooks, you can have a look at the React’s official hooks documentation, or check out “React Hooks: How to Get Started & Build Your Own”.

The useCallback() hook takes two arguments: the callback function, and a list of dependencies.

Consider the following example of useCallback():

const handleClick = useCallback(() => {
  //Do something
}, [x,y]);

Here, useCallback() is added to the handleClick() function. The second argument [x,y] could be an empty array, a single dependency, or a list of dependencies. Whenever any dependency mentioned in the second argument changes, only then will the handleClick() function be recreated.

If the dependencies mentioned in useCallback() don’t change, a memoized version of the callback that’s mentioned as the first argument is returned. We’ll change our parent functional component to use the useCallback() hook for the handler that’s passed to the child component:

//Parent.js
export default function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
  };

  const handler = useCallback(() => { //using useCallback() for the handler function
    console.log("handler");
  }, []);

  console.log("Parent render");
  return (
    <div className="App">
      <button onClick={handleClick}>Increment</button>
      <h2>{count}</h2>
      <Child name={"joe"} childFunc={handler} />
    </div>
  );
}

The child component code remains as it is.

The complete code for this example is shown below:

When we increment the count in the parent component for the code above, we can see the following output:

Parent render
Child render
Parent render
Parent render
Parent render

Since we used the useCallback() hook for the parent handler, every time the parent re-renders, the handler function won’t be recreated, and a memoized version of the handler is sent down to the child. The child component will do a shallow comparison and notice that handler function’s reference hasn’t changed — so it won’t call the render method.

Things to Remember

Memoization is a good technique for improving performance in React apps by avoiding unnecessary re-renders of a component if its props or state haven’t changed. You might think of just adding memoization for all the components, but that’s not a good way to build your React components. You should use memoization only in cases where the component:

  • returns the same output when given the same props
  • has multiple UI elements and a virtual DOM check will impact performance
  • is often provided the same props

Conclusion

In this tutorial, we’ve seen:

  • how React renders the UI
  • why memoization is needed
  • how to implement memoization in React through React.memo() for a functional React component and React.PureComponent for a class component
  • a use case where, even after using React.memo(), the child component will re-render
  • how to use the useCallback() hook to avoid re-rendering when a function is passed as props to a child component.

I hope you’ve found this introduction to React memoization useful!

Frequently Asked Questions (FAQs) about Memoization in React

What is the main purpose of using memoization in React?

Memoization in React is primarily used to optimize the performance of your application. It is a programming technique that stores the result of expensive function calls and reuses the returned result when the same inputs occur again. This means that if a function is called with the same arguments, instead of executing the function, the memoized result from the previous call is returned. This can significantly improve the performance of your application, especially when dealing with complex computations or rendering large lists or grids.

How does React.memo work?

React.memo is a higher-order component that you can use to wrap your functional components. It performs a shallow comparison of the current and new props and decides whether to re-render the component or not. If the props are the same, React.memo will prevent the re-rendering of the component, thus saving computational resources and improving performance.

When should I use memoization in my React application?

Memoization should be used in React applications when you have components that render expensive computations, or when you have components that render frequently with the same props. However, it’s important to note that memoization is not always necessary and can even lead to performance degradation if used improperly. Therefore, it’s crucial to profile your application and identify the bottlenecks before deciding to use memoization.

Can I use memoization with class components in React?

Yes, you can use memoization with class components in React. However, instead of using React.memo, you would use PureComponent or shouldComponentUpdate lifecycle method. PureComponent performs a shallow comparison of the current and new props and state, similar to React.memo. On the other hand, shouldComponentUpdate allows you to control the re-rendering of your component by comparing the current and new props and state.

What is the difference between React.memo and useMemo?

React.memo and useMemo are both used for optimization in React, but they serve different purposes. React.memo is a higher-order component that prevents the re-rendering of your component when the props are the same. On the other hand, useMemo is a hook that returns a memoized value, preventing expensive computations from running unnecessarily. useMemo is used within a component to memoize a value, while React.memo is used to wrap a component.

How does memoization improve the performance of my React application?

Memoization improves the performance of your React application by preventing unnecessary re-rendering of components and unnecessary computations. When a component is wrapped with React.memo or a value is memoized with useMemo, React will not re-render the component or recompute the value unless the inputs change. This can significantly reduce the computational resources needed by your application, leading to smoother and faster performance.

Are there any downsides to using memoization in React?

While memoization can significantly improve the performance of your React application, it’s not without its downsides. Memoization can lead to increased memory usage as the results of function calls are stored for later use. Additionally, if used improperly, memoization can lead to performance degradation instead of improvement. Therefore, it’s crucial to profile your application and identify the bottlenecks before deciding to use memoization.

Can I use memoization with hooks in React?

Yes, you can use memoization with hooks in React. The useMemo hook is specifically designed for this purpose. It returns a memoized value, preventing expensive computations from running unnecessarily. Similarly, the useCallback hook returns a memoized callback, which can be useful when passing callbacks to optimized child components.

How do I decide whether to use React.memo or useMemo?

The decision to use React.memo or useMemo depends on what you’re trying to optimize. If you’re trying to prevent a component from re-rendering unnecessarily, you would use React.memo. On the other hand, if you’re trying to prevent an expensive computation from running unnecessarily, you would use useMemo. It’s also important to note that these are not mutually exclusive and can be used together in the same component.

What is the difference between shallow and deep comparison in the context of memoization in React?

Shallow and deep comparison are terms used to describe how React compares the current and new props or state. Shallow comparison means that React only compares the top-level properties, while deep comparison means that React compares all the properties, including nested ones. React.memo and PureComponent use shallow comparison, which is faster but can lead to unnecessary re-rendering if the props or state have nested properties.

Nida KhanNida Khan
View Author

I am a full stack developer from India. I am a hands-on learner, hence prefer learning new technologies through development. In my free time, I explore new technologies, read tech blogs and solve problems on Data Structures and Algorithms.

memoizationReact
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week