React Hooks - useMemo, useCallback, React.memo

Introduction

This will be a new series where I will explain concepts related to React.js and do my best to explain these complex concepts in the simplest ways. React Hooks series will contain information about the most important React hooks that are often used in projects by developers. Without further ado, let us start now.

useMemo

Before we try to understand what useMemo does, we need to understand what memoization is. You must have heard about memoization while learning about Dynamic Programming in Data Structures and Algorithms. It is all about caching the results so that we don't have to evaluate a complex expression for the same input.

Often in React, we face a problem where within a component we happen to call a function which takes a very long time to evaluate an expression and that contributes to the degradation in the performance of an application. To understand that in-depth let us look at an example.

import { useState, useMemo } from "react";

export default function App() {
    const [ number, setNumber ] = useState();

    const doubleNumber = useMemo(() => {
        return slowFunction(number);
    }, [ number ]);

    return (
        <>
            <input type="number" value={number} onChange=    {()=>setNumber(e.target.value)} />
            <div>{doubleNumber}</div>
        </>
    );
}

function slowFunction(num) {
    for(let i = 0; i < 100000000000; i++) {}
    return num * 2;
}

In the above example, what is essentially happening is that there's a function called "slowFunction" which takes an input which is a number, and doubles and then returns it. If you look at it, in the function, there is for loop which runs for "100000000000" times before the value is returned. This can cause an impact on the performance of the application. In fact, if you run the application and type any number into the input field, you will observe a small delay or lag before the result is shown in the UI.

The issue is every time the component renders this is going to happen. So to prevent this from happening, the useMemo hook is used, such that for the same input, instead of re-evaluating the results, it provides the cached results, thereby improving the performance of the application.

Let us look at another application of the useMemo hook:

import { useMemo } from "react";

export default function Component({ param1, param2 }) {
    const params = useMemo(() => {
         return { param1, param2, param3: 5 };
    });

    useEffect(() => {
        callApi(params);
    }, [ params ]);

    return (
        //....something 
    );
}

In the above example, the useEffect has a dependency array, which contains the params object. So every time the component gets rendered, the useEffect is going to run because the object is being re-created and the reference to the object in memory is not the same as the one before the rendering of the component.

The useMemo hook in the example above caches the params object, which means the reference to the params object is going to be the same unless and until one of the dependencies changes i.e. param1 or param2 or both. Hence, the useEffect is prevented from running.

useCallback

The useCallback is similar to the useMemo, but the difference lies in the fact that while useMemo caches the result returned by a function, useCallback, on the other hand, returns a function, perhaps, a newer version of the function when the dependencies change otherwise it returns the same old function without having to re-create it every time the dependencies change.

Let us look at the example below to understand the difference between useCallback and useMemo:

useCallback(() => {
    return a + b; //useCallback returns the function itself
}, [a, b]);

useMemo(() => {
    return () => a + b; //this is a function whose result is being returned by the useMemo
}, [a, b]);

Let us now look at an application of the useCallback:

function Parent({ res }) {
    const [ items, setItems ] = useState(null);
    const handleLoad = useCallback((res) => setItems(res), [res]);

    return (
        <Child onLoad={handleLoad} />
    );
}

function Child(onLoad) {
    useEffect(() => {
        callApi(onLoad)
    }, [onLoad]);
}

In the above example, the Parent component returns a Child Component which takes in a prop called onLoad which is basically the handleLoad function. Inside the Child component, there is an useEffect which is run every time the component is rendered because every time the component is rendered, the function handleLoad is going to be created again. To prevent this from happening the useCallback is used where in the function won't be created again, and a new version of the function will be returned only when the dependencies change and that's when the useEffect inside the Child component will be run.

React.memo

React.memo is interesting as it takes the entire component as an input and prevents it from rendering unless the props passed to it are changed. However, it doesn't stop the rendering of the component if the internal states or the context changes.

React.memo(function Component(props) {
    //....something 
});

Final Thoughts

That's all about the useMemo, useCallback, and the React.memo hooks which are basically used to improve the performance of an application at large. In my upcoming blogs, I will be going over some more lesser used but important hooks in React. Till then, stay safe, be happy, and code furiously!

You can follow me on Github, LinkedIn.

Thank you for reading.