React - to memoize or not to memoize, that is the question!

Shyam KattiShyam Katti
6 min read

I often get asked about memoization in React during tech firm interviews: What are the performance benefits of memoizing a component? How can I measure these gains, if any? And when does it make sense to use or avoid memoization? Finally, decided to do a short write-up on it. You can find the reference to the official docs here.

How does React rendering work?

As you may know, one of the core features of the React framework is its Virtual DOM, which enables efficient UI rendering. The goal is to re-render a component only when a change is detected.

Let’s start with a simple example to demonstrate how rendering works in a component that contains child components.

We will define 3 components:App.jsx , ParentComponent.jsx and ChildComponent.jsx

import { useState, useEffect } from "react";
import ParentComponent from "./components/ParentComponent";
import './App.css';

function App() {

    useEffect(() => {
        console.log("Inside useEffect of  component: App");
    }, []);

    return (
        <ParentComponent>
            {console.log("Inside App.js HTML")}
        </ParentComponent>
    )
}

export default App
import {useEffect, useState} from "react";
import ChildComponent from "../ChildComponent/";
import "./ParentComponent.css";

const ParentComponent = ({}) => {


    useEffect(() => {
        console.log("Inside useEffect of  component: ParentComponent");
    }, []);

    return (
        <div className={"parent"} id={"parent-component"}>
            {console.log("Inside Parent component HTML")}
            <ChildComponent />
        </div>
    );
}

export default ParentComponent;
import React, { useEffect, useState } from 'react';
import "./ChildComponent.css";

const ChildComponent = ({}) => {

    useEffect(() => {
        console.log("Inside useEffect of  component: ChildComponent");
    }, []);

    return (
        <div className={"child"} id={"child-component"}>
            {console.log("Inside child component HTML")}
        </div>
    );
}

export default ChildComponent;

If you run the above code in the browser, the console log should show as follows:

Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of  component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App
Inside useEffect of  component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App

Notice the order - HTML code is rendered first from top to bottom and useEffect is executed from bottom to top.

Now, if we modify ParentComponent to have a state variable to force re-render, the ChildComponent also is re-rendered even though it has no dependency on the ParentComponent updates. Let’s update the code to check that flow now.

Updates to ParentComponent.jsx

import {useEffect, useState} from "react";
import ChildComponent from "../ChildComponent/";
import "./ParentComponent.css";

const ParentComponent = ({}) => {

    const [currCount, setCurrCount] = useState(0);

    useEffect(() => {
        console.log("Inside useEffect of  component: ParentComponent");

        const intervalId = setInterval(() => {
            setCurrCount(prevCount => prevCount + 1);
        }, 5000);
        return () => {clearInterval(intervalId)};

    }, []);

    return (
        <div className={"parent"} id={"parent-component"}>
            {console.log("Inside Parent component HTML")}
            <ChildComponent testProp={"testProp"} />
        </div>
    );
}

export default ParentComponent;
import React, { useEffect } from 'react';
import "./ChildComponent.css";

const ChildComponent = ({testProp}) => {

    useEffect(() => {
        console.log("Inside useEffect of  component: ChildComponent");
    }, []);

    useEffect(() => {
        console.log("Inside useEffect - 2 of component: ChildComponent");
    }, [testProp]);

    return (
        <div className={"child"} id={"child-component"}>
            {console.log("Inside child component HTML")}
        </div>
    );
}

export default ChildComponent;

On running the application, the console output looks like this:

Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of  component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App
Inside useEffect of  component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
......

As you can notice now, every update to a state variable in ParentComponent causes an additional render on ChildComponenteven though no props are updated.

This is where memoization comes into the picture. Let’s memoize the ChildComponent now to only re-render when a property has changed from ParentComponent.

Create a memoized ChildComponent

import React, { useEffect } from 'react';
import "./ChildComponent.css";

const ChildComponent = ({testProp}) => {

    useEffect(() => {
        console.log("Inside useEffect of  component: ChildComponent");
    }, []);

    useEffect(() => {
        console.log("Inside useEffect - 2 of component: ChildComponent");
    }, [testProp]);

    return (
        <div className={"child"} id={"child-component"}>
            {console.log("Inside child component HTML")}
        </div>
    );
}

const MemoizedChildComponent = React.memo(ChildComponent)
export default MemoizedChildComponent;

Rerun the project to see the console output:

Inside App.js HTML
Inside App.js HTML
Inside Parent component HTML
Inside Parent component HTML
Inside child component HTML
Inside child component HTML
Inside useEffect of  component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App
Inside useEffect of  component: ChildComponent
Inside useEffect - 2 of component: ChildComponent
Inside useEffect of  component: ParentComponent
Inside useEffect of  component: App
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
Inside Parent component HTML
......

As you notice now, the console line Inside child component HTML is no longer to be seen since the memoized child component restricts the redundant re-rendering. To ensure that re-rendering works when a state variable in the parent component changes, pass the variable currCount variable to the child component as a prop. I will leave it to you to make that change and see the code work :-).

What are the benefits of making ChildComponent memoized?

React DevTools is a great way to analyze what’s going on with one’s React application. Here is what I found after running this simple React application.

This is the performance without ChildComponent being memoized.

As you can see, every update in a state variable of ParentComponent also re-rendered ChildComponent thereby adding 0.6ms in time to completion. This might be small but look at it in the context of how much time it took ParentComponent to complete in the screenshot below.

As you can see, the ParentComponent subtree took 2.1ms to complete and the component itself took 1.1ms out of it. So we delayed the completion of ParentComponent by at least 50% more time due to the redundant rendering of the child components.

Now let’s see the performance once we have memoized the ChildComponent.

As you can see, the 2nd render doesn’t display the ChildComponent hence allowing ParentComponent to complete rendering in 1.7ms vs 2.1ms before which is approx. 20% reduction in time. Hence, memoization definitely helps in speeding up the render time if we figure out the components that are being re-rendered unnecessarily. This brings me to the next and last section of this article.

When does memoization help in React?

My rule of thumb is - measure first, optimize later.

The following scenarios usually indicate that memoization would be helpful:

  • The component needs complex or periodic calculations. For e.g. a component that needs to calculate the time left to reach the destination should only re-render if the distance to reach destination has changed.

  • The component that frequently received the same props. For e.g. a weather screen since the temperature is likely to change over a longer period.

However, one should also know when it has little to no benefits:

  • When a child component is not going to be updated due to any change from the parent component. E.g. static components which are part of a parent component.

  • When child component props change frequently, memoization doesn’t help since the re-rendering is unavoidable.

  • When child component is lightweight and cheap to render. E.g. a component with few text fields won’t add to your degraded performance.

Wrap Up

I hope this article gave you insights into how to use memoization and when it is likely to reap more benefits while writing your React code. Please do comment if you have any feedback on the article or have something interesting to share. Signing off from my end!

0
Subscribe to my newsletter

Read articles from Shyam Katti directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shyam Katti
Shyam Katti