useMemo hook

useMemo hook

Introduction

The useMemo is a hook in React that allows you to optimize the performance of your component by caching/memoizing/storing the results of a callback function and only re-evaluating/re-invoking it if one of the dependencies specified in the dependency array has changed.

The hook takes two arguments: a callback function that performs some operation and a dependency array that contains a list of dependencies on which the callback function is dependent to run. The dependency array can contain either state variables or props, or a combination of both.

When the component is first rendered/mounted (similar to the componentDidMount lifecycle method in a class component), the useMemo hook will run and execute the callback function.

If any of the dependencies specified in the dependency array change, the useMemo hook will re-run the callback function. However, if the dependencies have not changed, the useMemo hook will return the cached value from the previous render, rather than re-running the function.

The callback function passed to the useMemo hook is expected to return a calculated value, which will be stored in a variable. If you don't return a value from the callback function, React will throw a warning (but not an error), as the syntax is correct but the purpose of the useMemo hook is not being fulfilled.

syntax:

const cachedValue = useMemo(callbackfunction, dependencies)

useMemo() hook without any dependency array[]:

If you do not provide a dependency array as the second argument to the useMemo hook, it will behave like a combination of the componentDidMount and componentDidUpdate lifecycle methods like in a class component. This means that the callback function passed to useMemo will be executed whenever the component is mounted or updated*(any change in props or state will make react to re-render the component).*

1.componentDidMount(): This method will get invoked whenever a component gets mounted in the class component.

2.componentDidUpdate(): This method will get invoked whenever the component gets updated in the class component.

useMemo() hook behaving like componentDidMount() method:

A component that does not have any state variables (i.e., variables created using the useState hook) or that does not update the state of any variables will cause the useMemo hook to be invoked by React only once after the component is mounted.

let's take an example to understand it much better:

example 1:

let's create an App component with useMemo hook and pass a callback function as it's the first parameter in it, which prints "useMemo got invoked" in the console as shown below:

import { useMemo } from "react";
function App() {
  useMemo(() => {
    console.log("useMemo got invoked");
  });
  return (
    <>
      <h1>Welcome to App</h1>
    </>
  );
}
export default App;

output:

Here the useMemo is behaving similarly to componentDidMount() life cycle. it will get called only once that too only after App component gets mounted.

Note: if we observe the above code useMemo is invoked twice since we are in strict mode for debugging purposes but in production, it will be called once after we run npm run build command it will remove the strict mode.

example 2:

let's create an App component with useMemo hook and pass a callback function as it's the first parameter in it and assigned a variable useMemoVar, which prints "useMemo got invoked" in the console as shown below:

import { useMemo } from "react";
function App() {
  const useMemoVar = useMemo(() => {
    console.log("useMemo got invoked");
  });
  console.log(useMemoVar);
  return (
    <>
      <h1>Welcome to App</h1>
      <h1>useMemoVar = {useMemoVar}</h1>
    </>
  );
}
export default App;

output:

since we are not returning anything from the callback function, the useMemoVar will be undefined when we print it on the console and when we try to display it on the HTML page it will be empty as shown below.

Note: if we observe the above code useMemo is invoked twice since we are in strict mode for debugging purposes but in production, it will be called once after we run npm run build command it will remove the strict mode.

example 3:

let's create an App component with useMemo hook and pass a callback function as it's the first parameter in it and assigned a variable useMemoVar, which prints "useMemo got invoked" in the console and let's create a state variable count using useState hook as shown below:

import { useMemo, useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  const useMemoVar = useMemo(() => {
    console.log("useMemo got invoked");
  });
  console.log(useMemoVar);
  return (
    <>
      <h1>Welcome to App</h1>
      <h1>useMemoVar = {useMemoVar}</h1>
      <h1>count is {count}</h1>
    </>
  );
}
export default App;

output:

as we are not returning anything from the callback function, the useMemoVar will be undefined when we print it on the console and when we try to display it on the HTML page it will be empty.

as we have defined state variable count whose initial state is 0 using the UseState hook and not updating its state using the setCount method. The useMemo hook will get invoked/called only once by react that too only when App component gets mounted .

Note: if we observe the above code useMemo is invoked twice since we are in strict mode for debugging purposes but in production, it will be called once after we run npm run build command it will remove the strict mode.

useMemo() hook behaving like componentDidUpdate() method:

Each time when we update the state of any variable to a different state in a functional component or when we pass different props to a child component from a parent component, React will re-render that component which triggers the useMemo hook to get invoked/called which makes it behaves like componentDidUpdate() life cycle method in class component.

let's look at some examples to understand it much better

example1:

let's create a functional component called App which contains a useMemo hook where we pass a callback function as its first parameter which prints useMemo got invoked whenever it gets called and it also contains a state variable count which is created by using useState() hook and a button so that whenever the user clicks on the button it calls a callback function which increases the count variable by 1 which cause a change in the state of count variable like shown below.

import { useMemo, useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  useMemo(() => {
    console.log("useMemo got invoked");
  });
  return (
    <>
      <h1>Welcome to App</h1>
      <h1>count is {count}</h1>
      <button
        onClick={() => setCount((count) => count + 1)}
        style={{ backgroundColor: "yellow" }}
      >
        Click
      </button>
    </>
  );
}
export default App;

output:

before clicking on the click button.

after clicking on the click button.

Gotcha alert:

each time when user clicks on the button useMemo hook will run twice unlike in useEffect hook where it will only once. It is kinda of a bug for more info please refer

Issues 24935

example 2:

For this, we need 2 components, let's make 2 components one is the User and the other is the App.

where the App component is the parent component that sends userid to the user component which is a child component of the App and let's increase the userid by 1 whenever the user clicks on the button like shown below:

App.js


import { useState, useMemo } from "react";
import User from "./components/User";

function App() {
  const [userId, setUserId] = useState(0);
  useMemo(() => {
    console.log("useMemo Method inside App");
  });
  return (
    <>
      <h1>Welcome to App Component</h1>
      <br />
      <User userId={userId} />
      <button
        onClick={() => setUserId((userId) => userId + 1)}
        style={{ backgroundColor: "yellow" }}
      >
        Click
      </button>
    </>
  );
}
export default App;

User.js

import { useMemo } from "react";

function User(props) {
  useMemo(() => {
    console.log("useEffect Method inside User");
  });
  return (
    <>
      <h1>Userid is {props.userId}</h1>
    </>
  );
}
export default User;

Output:

before clicking on the button:

after clicking on the button:

How does useMemo() hook optimize the performance?

To understand how does useMemo optimize performance first we need to understand how Javascript and React renders work under the hood whenever state or props gets changed or updated?

Some Gotcha in Js to be noted before we jump into the topic:

Gotcha Alert:

In Javascript, for reference types like array and objects eventhought it contains same elements of same type like shown below.

  const obj1 = { ok: 1, p: 2 };
  const obj2 = { ok: 1, p: 2 };
  const a1 = [1,2,3];
  const a2 = [1,2,3];
  const i = 1;
  const j = 1;
  const s1 = "Indratej";
  const s2 = "Indratej";
  console.log(obj1 === obj2); //false
  console.log(Object.is(obj1, obj2)); //false
  console.log(a1===a2); //false
  console.log(i === j); //true
  console.log(s1===s2); //true

/*output: 
  false
  false
  false
  true
  true
*/

when we compare it using the equal (===) operator for reference type like arrays and objects Js will give output as false because Js compares the address/location in the memory for reference types.

so when we send non reference types like int,string etc. in useState hook like below example:

import { useState } from "react";
function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <h1>your count is {count}</h1>
      <button
        onClick={()=>setCount(count=>0)}
        style={{ backgroundColor: "black", color: "yellow" }}
      >
        Click ME
      </button>
    </>
  );
}
export default App;

output:

No matter how many times we click on the button state never changes as we are sending non reference type which is an integer.

before clicking:

after clicking:

count is always updating to 0 and since it is a non reference type which is int. useState will not see any state change so react will not re-render the component.

if we slightly modify the same code with object like shown below:

import { useState } from "react";
function App() {
  const [count, setCount] = useState(0);


  return (
    <>
      <h1>your count is {count}</h1>
      <button
        onClick={()=>setCount(count=>0)}
        style={{ backgroundColor: "black", color: "yellow" }}
      >
        Click ME
      </button>
    </>
  );
}
export default App;

let us take an example to understand it much better.

let's create an App component that contains a state variable count and a button that invokes a call-back function to update the state of the count by increasing its value by 1.

let's create a function with the name complex function and let's perform some complex operations like

import { useState } from "react";
function App() {
  const [count, setCount] = useState({data:0});
  function complexFunction(){
    console.log("fun is invoked");
  }
  complexFunction();
  return (
    <>
      <h1>your count is {JSON.stringify(count)}</h1>
      <button
        onClick={()=>setCount((count)=>({data:0}))}
        style={{ backgroundColor: "black", color: "yellow" }}
      >
        Click ME
      </button>
    </>
  );
}
export default App;

output:

before click:

after click:

Now let's see how useMemo() works smartly to optimize the code.

As we have learned that useState for object input makes react to re-render the component.

In the real world from the backend, most of the time data will send in the form of JSON which is a kind of object in Js. so most of the time props or state variables sent from the backend will be in object type.

now let's take look at the previous example:

import { useState } from "react";
function App() {
  const [count, setCount] = useState({data:0});
  function complexFunction(){
    console.log("fun is invoked");
  }
  complexFunction();
  return (
    <>
      <h1>your count is {JSON.stringify(count)}</h1>
      <button
        onClick={()=>setCount((count)=>({data:0}))}
        style={{ backgroundColor: "black", color: "yellow" }}
      >
        Click ME
      </button>
    </>
  );
}
export default App;

So in the above code each time when user clicks on the button. we are sending the same state for the count variable which is {data:0} but yet react will re-render the component which makes all the functions in the component get called/invoked again and again each time when the user clicks on the button what if we got complex functions that are returning the same value again as the props/state is the same? here is the picture of useMemo hook, which will keep the track of state/props passed in the dependency array, so rather than calling the same function, again and again, to retrieve the same value again and again. the useMemo hook will store the return value in the variable to reuse and stops invoking the call back if the props/state match with the props/state passed inside the dependency array of useMemo.

Now let's modify the above code using the useMemo() hook shown below:

import { useState, useMemo } from "react";
function App() {
  const [count, setCount] = useState(0);
  const complexValue = useMemo(() => {
    console.log("complexFunction is invoked");
    return count + 1;
  }, [count]);
  return (
    <>
      <h1>your count is {count}</h1>
      <button
        onClick={() => setCount((count) => 1)}
        style={{ backgroundColor: "black", color: "yellow" }}
      >
        Click ME
      </button>
    </>
  );
}
export default App;

output:

before click:

after click:

after clicking 1 time, the state changes count increases to 1 shown below:

now let's click again and again no matter how many times we click on the button, the count will update to 1 again and again but the component will re-render but the complexFunction will not run as we placed it inside the useMemo hook will remember its previous state 1. so directly return the value 2.

Memoizing a function:

If you wrap the Form component in memo, and you want to pass a function to it as a prop, here is how you could rephrase this:

"If the Form component is wrapped in memo, and you want to provide it with a function via a prop, you can do so by passing the function as an argument to the Form component."

export default function ProductPage({ productId, referrer }) {
  function handleSubmit(orderDetails) {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails
    });
  }

  return <Form onSubmit={handleSubmit} />;
}

Similar to how {} always creates a different object, function declarations like function() {} and expressions like () => {} produce a different function on every re-render. By itself, creating a new function is not a problem. This is not something to avoid! However, if the Form component is memoized, presumably you want to skip re-rendering it when no props have changed. A prop that is always different would defeat the point of memoization":

"While creating a new function with declarations like function() {} or expressions like () => {} on every re-render is not necessarily an issue, it can cause problems when the Form component is wrapped in memo in order to optimize performance by avoiding unnecessary re-renders. In this case, using a prop that is always different (such as a new function) would negate the benefits of memoization by triggering re-renders even when other props have not changed."

To memoize a function with useMemo, your calculation function would have to return another function:

export default function Page({ productId, referrer }) {
  const handleSubmit = useMemo(() => {
    return (orderDetails) => {
      post('/product/' + product.id + '/buy', {
        referrer,
        orderDetails
      });
    };
  }, [productId, referrer]);

  return <Form onSubmit={handleSubmit} />;
}

it is not recommended to use the useMemo hook to memoize a function. Instead, you should use the useCallback hook for this purpose.

The useCallback the hook is similar to useMemo, but it is specifically designed for memoizing functions. It takes a function and an array of dependencies as arguments, and it returns a memoized version of the function that only changes if one of the dependencies has changed.

Here is an example of how you might use useCallback to memoize a function in a React component:

Copy codeimport { useCallback } from 'react';

function MyComponent() {
  const myFunction = useCallback(() => {
    // function body goes here
  }, [dependency1, dependency2]);

  // use myFunction as needed in the component
}

This looks messy, Memoizing functions are common enough that React has a built-in Hook specifically for that. Wrap your functions into useCallback instead of useMemo to avoid having to write an extra nested function":

"Using useMemo to memoize functions can be awkward and unnecessarily complex. Fortunately, React provides a built-in hook called useCallback specifically designed for this purpose. By using useCallback instead of useMemo, you can avoid having to nest an extra function and achieve the same result in a more streamlined way.

export default function Page({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + product.id + '/buy', {
      referrer,
      orderDetails
    });
  }, [productId, referrer]);

  return <Form onSubmit={handleSubmit} />;
}

Advantages of useMemo hook:

Best practices:

Generally parent component uses useMemo hook and whereas the child component which gets props from the parent component will use the memo hook.

All the dependencies of the callback function must be placed inside the callback other wise when re-rendering the component, the variable which is the reference type will change and make the callback run again and again.

example:

function Dropdown({ allItems, text }) {
  const searchOptions = { matchMode: 'whole-word', text };

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
  // ...

searchOptions can be put inside useMemo. because if text may change.

Depending on an object like this defeats the point of memoization. When a component re-renders, all of the code directly inside the component body runs again. The lines of code creating the searchOptions object will also run on every re-render. Since searchOptions is a dependency of your useMemo call, and it’s different every time, React will know the dependencies are different from the last time, and recalculate searchItems every time.

To fix this, you could memoize the searchOptions object itself before passing it as a dependency:

function Dropdown({ allItems, text }) {
  const searchOptions = useMemo(() => {
    return { matchMode: 'whole-word', text };
  }, [text]); // ✅ Only changes when text changes

  const visibleItems = useMemo(() => {
    return searchItems(allItems, searchOptions);
  }, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
  // ...

this can be further optimized like below:

In the example above, if the text did not change, the searchOptions object also won’t change. However, an even better fix is to move the searchOptions object declaration inside of the useMemo calculation function:

function Dropdown({ allItems, text }) {
  const visibleItems = useMemo(() => {
    const searchOptions = { matchMode: 'whole-word', text };
    return searchItems(allItems, searchOptions);
  }, [allItems, text]); // ✅ Only changes when allItems or text changes
  // ...