Skip to main content
React useMemo Hook Guide with Examples
13 min read

React useMemo Hook Guide with Examples

This article was last updated on September 19, 2024, to add sections on the latest changes to the React useMemo API and provide more detailed explanations of how useMemo() works.

Introduction

This post is about the useMemo() hook in React. We demonstrate the use of useMemo for caching the value of expensive functions in a React app and examine the impact from the browser console.

This is the second post in the three part React Memoization Series hosted on Refine.dev blog.

The three parts are:

  1. React Memo Guide with Examples
  2. React useMemo Hook Guide With Examples
  3. Memoization in React - How useCallback Works

In this post, we dive into the details of the useMemo hook with an extension of the example demonstrated in the first post titled React Memo Guide with Examples.

Steps we'll cover:

What is React useMemo ?

React useMemo() hook is a function that caches the value produced from an expensive function used inside a React component. It accepts the expensive function and works by storing the value produced from the function when that is passed the same arguments repeatedly. When different arguments are passed, it returns the new value and updates the cache.

Caching an expensive function's value with useMemo is useful in optimizing the performance of a React component - as doing so minimizes repeating heavy computations.

Resource Intensive Functions in React: Why Use React useMemo ?

An expensive function is typically a resource intensive function that performs heavy and repetitive computations. In React, data processing, transformation and manipulation utilities like sorting functions, filters and mappers are commonly used costly functions. Such data heavy functions consume a lot of application resources (memory and time, which cost the application with billing). They slow down a React application and contributes to poor performance.

useMemo's caching helps bypass the expensive function's repetitive heavy processes when it is invoked with the same parameters - thereby improving performance of the React component.

Optimizing Expensive Utility Functions with React useMemo Hook

In the sections that follow, we demonstrate the use of React useMemo hook in a demo blog app that we explored in Part I. Part I demonstrates the use of React.memo for memoizing a component, i.e. the <Post /> component.

In this post, we'll implement caching of a sorting function with useMemo() and examine how it prevents recalculation tasks from the browser's console.

But before that, let's get a refresher of what's going on in the demo app.

Project Overview

We recommend you follow along from Part I: React Memo Guide with Examples. This way, you should already have the app cloned, opened in a code editor, installed, and up and running in a browser. You should also be familiar with the components before we make the changes with useMemo.

The demo app is based on the idea of a list of posts on a blog. There are several components involving a user seeing the latest posts and a list of the user's posts. Allow yourself some time to understand the components individually, their relationships and their state changes - especially for this post, the <App /> and<Blog /> components.

Take a particular note of the sorting function in src/utils/sortPosts.js, which is used for sorting posts inside <Blog />.

Here you can find the example app's live code

The discussion of this post is focused on optimizing a React component's performance by memoizing the value of resource intensive functions, such as sortPosts(). React's useMemo() hook is intended for this purpose.

Code Investigation

Let's start with examining what's happening in the <App /> component. It looks like this:

src/App.tsx
import { useState } from "react";
import Blog from "./components/Blog";

function App() {
const [signedIn, setSignedIn] = useState(false);
const handleClick = () => setSignedIn(!signedIn);

// console.log('Rendering App component');

return (
<main>
<nav className="navbar">
<button className="btn btn-danger" onClick={handleClick}>
Sign Out
</button>
</nav>
<Blog signedIn={signedIn} setSignedIn={setSignedIn} />
</main>
);
}

export default App;

As we can see, the <App /> component houses the <Blog /> component.

<Blog /> looks like this:

src/components/Blog.jsx
import React, { useEffect, useMemo, useState } from "react";
import fetchUpdatedPosts from "../fetch/fetchUpdatedPosts";
import allPosts from "./../data/allPosts.json";
import sortPosts from "../utils/sortPosts";
import LatestPost from "./LatestPost";
import UserPostsIndex from "./UserPostsIndex";

const Blog = ({ signedIn }) => {
const [updatedPosts, setUpdatedPosts] = useState(allPosts);
const [localTime, setLocalTime] = useState(new Date().toLocaleTimeString());

const getLatestPosts = () => {
const posts = fetchUpdatedPosts();
setUpdatedPosts(posts);
};

const sortedPosts = sortPosts(updatedPosts);
// const sortedPosts = useMemo(() => sortPosts(updatedPosts), [updatedPosts]);

useEffect(() => {
const id = setInterval(
() => setLocalTime(new Date().toLocaleTimeString()),
1000,
);
return () => clearInterval(id);
}, []);

console.log("Rendering Blog component");

return (
<div className="container">
<h1 className="heading-lg m-1 p-1 text-center">Memoization in React</h1>
<div className="m-1 p-2 ">
<div className="box my-1 p-2">
<div className="latest-posts-top">
<h3 className="heading-md my-1 p-1">Latest posts</h3>
<div className="p-1">{localTime}</div>
</div>
<div className="my-1">
<button className="btn btn-primary" onClick={getLatestPosts}>
Get&nbsp;Latest&nbsp;Post
</button>
</div>
<hr className="hr my-2" />
<LatestPost signedIn={signedIn} post={sortedPosts[0]} />
</div>
<UserPostsIndex signedIn={signedIn} />
</div>
</div>
);
};

// export default Blog;
export default React.memo(Blog);

We'd like to focus particularly on the sortPosts() utility function which is used to sort posts stored in updatedPosts. sortPosts can get expensive if passed a long array of posts.

We are fetching the posts with the fetchUpdatedPosts function used inside getLatestPosts. At the moment, we are only sorting 101 items returned from fetchUpdatedPosts(), but in a real application, the number can be much higher and consume resources at scale. Thus, sortPosts is an expensive function.

Things get worse when this component re-renders due to parent re-renders or the component's internal state changes.

For example, if we look inside the useEffect() hook, we are updating the locale time string and storing it in localTime for our clock. localTime updates every second, and each state change in localTime triggers a re-render of <Blog />.

The clock does not represent a genuine UI feature for us here, but it is there to make a point about how frequent re-renders complicate things with expensive utility functions. It gives the same effect as state changes on a social media feed like those in LinkedIn or Facebook.

Our sortPosts() logs Sorting posts... to the console and returns a sorted array from the passed in an array:

src/utils/sortPosts
const sortPosts = (posts) => {
console.log("Sorting posts...");
return posts.sort((a, b) => b.id - a.id);
};

export default sortPosts;

If we look at the console, we see that Sorting posts... is being logged at 1000ms intervals, i.e. with the tick of our clock:

react usememo

This shows sortPosts() is called at every re-render of <Blog /> due to localTime state updates. An expensive function, invoked every second for no useful reason, is costly for the app. We don't want sortPosts() to be called if updatedPosts is not changed.

So, we need to leverage useMemo() hook in order to memoize sortPosts so that it does not execute anytime other than when updatedPosts changes.

React useMemo: How to Cache the Value of Expensive Utilities

useMemo() helps us cache the value of sortPosts() between <Blog />'s re-renders. This will prevent its unnecessary execution when updatedPosts remains unchanged while re-renders keep happening due to changes in localTime.

So, in <Blog />, Let's use the memoized version of sortPosts function:

src/components/Blog.jsx
-- const sortedPosts = sortPosts(updatedPosts);
++ const sortedPosts = useMemo(() => sortPosts(updatedPosts), [updatedPosts]);

After this change, examining our browser console, we can see that Sorting posts... has been logged only once, indicating only one invocation of sortPosts() even though the component keeps re-rendering every second:

react usememo

This gives us a huge performance gain.

Using React useMemo with Dependencies

Notice the dependency array of useMemo(), passed as the second argument, updatedPosts. We are asking the hook to re-execute sortPosts() and renew the memo when updatedPosts changes.

Let's now try to change the value of updatedPosts. In the <Blog/> component, we have a Get Latest Post button, which is used to fetch latest posts on demand. Every time Get Latest Post button is clicked, updatedPosts is updated with the invocation of getLatestPosts().

When the state of updatedPosts is changed, a re-render of <Blog /> is triggered, which leads to a call to sortPosts() with the new value of updatedPosts passed in.

If we check our console while clicking the button, we can clearly see Sorting posts... being logged for each click:


react usememo

This indicates that sotPosts() executes and renews the cache when its dependency, updatedPosts, gets updated.

INFORMATION

When to Use React useMemo()

It is important to notice that, if we remove the dependency from useMemo(), sortPosts() will not be invoked when updatedPosts change:

const sortedPosts = useMemo(() => sortPosts(updatedPosts), []);

In other words, there is no sorting going on when we actually need it:

react usememo

So it is important that we leverage useMemo when the execution of the utility function depends on relevant state changes. In our case, execution should depend on updatedPosts.

It is also important to know that useMemo returns a value, as opposed to a function. In other words, React useMemo should be used only to memoize the value returned from a function, not the function itself.This is what differentiates it from the useCallback() hook, which returns a memoized function.

When Not to Use React useMemo

useMemo() is preferred for memoizing a value rather than a callback function. We should not use useMemo for memoizing a function such as a callback.

More Use Cases for useMemo() Hook

I wanted to share some advanced use cases for the useMemo() hook that might be useful in our projects. I’ve added some code examples for clarity.

Handling Large Data Processing

It's great for situations when we’re working with large datasets (like sorting or filtering) and want to avoid recalculating the data when it hasn’t changed. Here’s an example of sorting a big list:

import React, { useMemo } from "react";

const LargeDataComponent = ({ data }) => {
const sortedData = useMemo(() => {
console.log("Sorting data...");
return data.sort((a, b) => a.value - b.value);
}, [data]);

return (
<div>
{sortedData.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};

In this case, sortedData will only be recalculated when data changes. Otherwise, the cached sorted result is reused.

Improving API Responses

We can use useMemo() to cache API responses, so we avoid making the same API calls multiple times with the same parameters. Here's how:

import React, { useMemo, useState, useEffect } from "react";

const fetchData = async (query) => {
const response = await fetch(`https://api.example.com/data?search=${query}`);
return response.json();
};

const APIComponent = ({ query }) => {
const [data, setData] = useState([]);

const memoizedData = useMemo(() => {
return fetchData(query);
}, [query]);

useEffect(() => {
memoizedData.then((result) => setData(result));
}, [memoizedData]);

return (
<div>
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};

Here, useMemo() stores the API response based on the query. The API will only be called again when the query changes, saving resources.

Pagination and Data Display

useMemo() can also be useful for pagination to cache already loaded pages of data, avoiding unnecessary recalculations when flipping through pages. Here’s an example:

import React, { useMemo, useState } from "react";

const PaginationComponent = ({ data, currentPage, itemsPerPage }) => {
const paginatedData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return data.slice(startIndex, endIndex);
}, [currentPage, data, itemsPerPage]);

return (
<div>
{paginatedData.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
};

Here, we are only recalculating the data for the current page and not reprocessing the entire dataset each time the page changes.

Bonus: Best Practices for Using useMemo() in React

I thought I'd share some best practices for using useMemo() in React to help optimize our components without overcomplicating the code. Here are a few key points:

Use useMemo() Only for Expensive Calculations

The main purpose of useMemo() is to avoid re-executing expensive functions unnecessarily. If the calculation isn't costly performance-wise, it's better not to use useMemo(), as it adds extra complexity.

const sortedData = useMemo(() => {
return data.sort((a, b) => a.value - b.value);
}, [data]);

Here, sorting large data is expensive, so memoizing the result is useful. However, if the operation is lightweight, useMemo() isn't necessary.

Avoid Overusing useMemo()

Using useMemo() too much can make the code harder to read and maintain. The rule of thumb is to only apply it where it truly improves performance.

Example (Not needed):

const simpleCalculation = useMemo(() => {
return number * 2;
}, [number]);

In this case, multiplying a number by 2 is inexpensive, and using useMemo() here would be unnecessary.

Always Include Dependencies Properly

Ensure that all necessary dependencies are included in the array to prevent unwanted bugs. useMemo() will only recalculate when one of the dependencies changes, so missing a dependency can lead to stale data or incorrect behavior.

Example:

const filteredData = useMemo(() => {
return data.filter((item) => item.active);
}, [data]); // Always ensure the dependency (data) is included

Don’t Use useMemo() to Memoize Functions

useMemo() is for memoizing values, not functions. If you need to memoize a function, use the useCallback() hook instead.

Correct Example (using useCallback()):

const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);

Use it for Expensive Object or Array Creation

If you’re creating complex objects or arrays in your component, especially those that involve computations, useMemo() can help by memoizing the created object or array so it’s not recreated on every render.

const userPreferences = useMemo(() => {
return { theme: "dark", language: "en" };
}, []);

Use useMemo() for Derived State

When the state is derived from props or other states, useMemo() can prevent unnecessary recalculations.

const fullName = useMemo(() => {
return `${firstName} ${lastName}`;
}, [firstName, lastName]);

Here, useMemo() ensures that fullName is only recalculated when firstName or lastName changes.

  • Use useMemo() for expensive calculations (like sorting, filtering).
  • Avoid overusing it—only apply where performance gain is significant.
  • Always include dependencies accurately.
  • Use useCallback() for memoizing functions, not useMemo().
  • Apply useMemo() for heavy object or array creation.

Summary

In this post, we demonstrated the use of React useMemo() hook and examined how it plays a crucial role in optimizing the performance of a component by memoizing an expensive utility function. We noticed that it is important to specify the dependencies of useMemo so that the memo is re-computed and renewed when the state of the dependencies change.

We also learned that the useMemo hook should be used to cache values of a function and not for memoizing the function itself.

In the next post in the React Memoization Series, we demonstrate function memoization using the useCallback() hook.

Live Example

Run on your local
npm create refine-app@latest -- --example blog-react-memoization-memo