React Hooks: useMemo (With Practical Examples)

Tito AdeoyeTito Adeoye
7 min read

Hello again, fellow React traveler👋 If you’ve been building React apps for a while, you might have encountered performance hiccups, especially as your app grows in complexity. One of the most useful tools in your React toolbox to combat these is the useMemo hook. But what exactly is it, and how can it help your app run faster?

In this article, we’ll dive into what useMemo is, when to use it, and how it can make your React apps more efficient. Let’s get started!

An Introduction

In React, rendering in response to changes is at the core of how your app works. However, with each render, React must recompute certain values which can lead to performance issues as complexity increases.
That’s where useMemo comes in. Just like useState allows you to store and persist values across renders, useMemo helps you store the result of a calculation so that React only recalculates it when necessary. Think of it as a way to "remember" values between renders, ensuring that expensive calculations don’t run every single time.

If you’ve already explored useState and useEffect with us, you might be familiar with how dependencies work in useEffect—only re-running when a specific value changes. In our example below, the alert() method only runs on intitial render and when isNewUser changes and is found to be true. Changes to the loading state variable would not cause it to run.

const App = ({isNewUser}) => {
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        isNewUser && alert('Please change your password');
    }, [isNewUser]);

    return (
        // ...code
    );
};

useMemo follows a similar principle. It memoizes the value of a variable and re-computes it only when defined dependencies change, reducing unnecessary recalculations. To put it simply, it caches the result of a calculation between re-renders.
It is called similarly to the useEffect hook but unlike the useEffect hook, there is a returned result, which is typically stored in an identifier, just like a regular variable.

import { useMemo } from 'react';
import {all_countries} from './constants';

function Locations({ todos, tab }) {
  const [activeCountry, setActiveCountry] = useState(all_countries[0].name);
  // ...other state variables

  const states = useMemo(
    () => all_countries.filter(country => country?.name === activeCountry).states
    ,[activeCountry, all_countries]
  );

  return (
    <section>
        <div className='flex flex-col'>
            {all_countries.map(country => 
                <button 
                    key={country.id}     
                    onClick={() => setActiveCountry(country.name)}
                >
                    {country.name}
                </button>
            )}
        </div>
        <div className='flex flex-col'>
            {cities.map(city => 
                <p key={city} >
                    {city}
                </p>
            )}
        </div>
    </section>
  );
}

Therefore, in our example, when our component is re-rendered, cities is not recomputed, even when other state changes cause our component to be re-rendered, the caveat being that our defined dependencies do not change. Instead its cached result is returned. The cities variable is only recalculated when the user clicks on a button, which updates the activeCountry state.
If our cities variable was not memoized, every re-render would cause the cities variable to be recomputed which could lead to performance issues. Now in a small application where your array is relatively small or where your value’s dependencies rarely change, you might think there is no need to memoize your component and you’d be right, but imagine you’re dealing with large datasets—think thousands or millions of rows. When used in components that regularly update or re-render, computer resources will be wasted recalculating results every time, leading to slow performance and unnecessary strain on both the browser and the user experience. Memoizing such computations ensures that only the necessary recalculations happen, drastically improving the responsiveness of your app.

Applications of useMemo

  1. Expensive Computation

    Like we just discussed in the example above, by using useMemo, React will only recompute values when the dependencies change, preventing unnecessary recalculations on re-renders. This can significantly reduce CPU usage in larger applications. Note: an empty dependencies array would cause the values to be computed only once. It will only be recomputed when the component is unmounted and mounted again (for instance, when you reload the page).

  2. Preventing Recursive Re-renders in Child Components

    In more complex component trees, re-renders can cascade down from the parent to child components. If a component’s props are recalculated on each render (even if they haven’t changed), it can lead to recursive re-renders where both the parent and its children are constantly updating.

    In the following example, we have a parent component that passes a calculatedData prop to the child. Without memoization, even when the prop hasn't changed, the child will still re-render:

     import React, { useState, useEffect } from 'react';
    
     const Parent = () => {
         const [data, setData] = useState([1, 2, 3]);
         const [count, setCount] = useState(0);
    
         // expensive calculation (e.g., sorting data)
         const calculatedData = data.sort((a, b) => a - b);
    
         useEffect(() => {
             console.log('Parent re-rendered');
         }, [data]);
    
         return (
             <div>
                 <button onClick={() => setCount(count + 1)}>Increment Count</button>
                 <Child calculatedData={calculatedData} />
             </div>
         );
     };
    
     const Child = React.memo(({ calculatedData }) => {
         console.log('Child re-rendered');
         return (
             <ul>
                 {calculatedData.map((num, idx) => (
                     <li key={idx}>{num}</li>
                 ))}
             </ul>
         );
     });
    

    In this case, every time you click the "Increment Count" button in the parent, it triggers a re-render of the parent and the child. Even though the calculatedData hasn’t changed (it’s still the same sorted data), the child re-renders because the calculatedData is re-calculated each time the parent re-renders.

    If we memoize calculatedData with useMemo, React will only recompute the sorted data when the data array changes. This prevents unnecessary re-renders of the child component.

     const calculatedData = useMemo(() => data.sort((a, b) => a - b), [data]);
    
  3. Large datasets: In cases where you work with large datasets that are passed down through multiple layers of components, memoizing those values can greatly improve performance.

  4. Preventing an Effect from firing too often: useEffect is used to run side effects in your components, such as fetching data, subscribing to events, or updating external libraries. However, every time a dependency in the effect changes, React triggers that effect again. This can lead to performance issues if the effect depends on calculations or data that don’t need to change on every render.

    By memoizing expensive calculations or values that are passed as dependencies to useEffect, we can prevent unnecessary or overly frequent firing of the effect, improving the performance of the application.

    Imagine you have a chart component that takes some dynamic props (like a theme and data) and requires a configuration object to render the chart. The configuration object is derived from the props and passed down to a charting library. If the configuration object is recreated on every render, it could cause unnecessary re-renders of the chart, even if the underlying data and theme haven’t changed.

     import React, { useState, useEffect } from 'react';
     import { LineChart } from 'some-chart-library';
    
     function ChartComponent({ theme, data }) {
       const [chartConfig, setChartConfig] = useState(null);
    
       // create chart configuration object dynamically based on props
       const config = {
         type: 'line',
         theme: theme,
         data: data
       };
    
       useEffect(() => {
         // initialize the chart with the config object
         const chartInstance = new LineChart(config);
         chartInstance.render();
    
         return () => {
           chartInstance.destroy();
         };
       }, [config]); 
    
       return <div id="chart-container" />;
     }
    

    The problem here is that config is a new object on every render, causing the effect to run again, and the chart to be re-initialized every time the component re-renders, even when there’s no real change in the theme or data.
    Memoizing the config object means React will reuse the same object reference between renders unless theme or data changes. As a result, the chart configuration is only recalculated when the actual dependencies (theme or data) change.

    A similar example is an effect that connects you to a chatroom using some dynamic props e.g. a roomId, to create the object needed for the connection. Memoizing the object and making the roomId a dependency, prevents unnecessary side effects, such as re-establishing the connection to the chat room on every render.

     import React, { useState, useEffect, useMemo } from 'react';
    
     function ChatRoom({ roomId }) {
       const [message, setMessage] = useState('');
    
       // memoize the options object
       const options = useMemo(() => {
         return {
           serverUrl: 'https://localhost:1234',
           roomId: roomId
         };
       }, [roomId]); // only changes when roomId changes
    
       useEffect(() => {
         const connection = createConnection(options);
         connection.connect();
    
         return () => connection.disconnect();
       }, [options]); // only runs when options changes
    
       return (
         <div>
           <input 
             type="text" 
             value={message} 
             onChange={(e) => setMessage(e.target.value)} 
             placeholder="Type a message"
           />
           {/* Render messages here */}
         </div>
       );
     }
    

Conclusion

By understanding these examples and using useMemo appropriately, you can prevent unnecessary re-renders, reduce recursion, and optimize the performance of your React applications!

Happy Coding🙌

1
Subscribe to my newsletter

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

Written by

Tito Adeoye
Tito Adeoye