Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 60

Optimizing with useMemo: Memoizing Expensive Computations

Recommended resources

Hello! In our last lesson, we explored the useRef hook, a tool for accessing DOM elements directly for imperative actions like managing focus. We saw that it provides an "escape hatch" from React's declarative world when you need it.

Today, we'll dive into another powerful hook, but one that's focused squarely on performance: useMemo. This lesson will teach you how to use useMemo to prevent expensive, unnecessary calculations that can slow down your application, ensuring a smooth and responsive user experience. This is a crucial skill for building the fast, scalable e-commerce app you're aiming for.

1. The Problem: Expensive Calculations on Every Render

As you know, a React component re-renders whenever its state or props change. This is usually very fast. However, a problem arises if your component performs a "heavy" or "expensive" calculation on every render.

Imagine a component that does two things:

  1. Calculates a complex, time-consuming value (e.g., filtering thousands of data points, a complex mathematical operation).
  2. Manages a simple piece of state, like a text input.

Every time you type a character into the input, the component's state updates, triggering a re-render. During this re-render, the expensive calculation runs again, even though its inputs haven't changed. This can make your UI feel slow and laggy.

To see this problem in action, let's watch a short video that sets up this exact scenario using a recursive Fibonacci function as our "expensive calculation."

useMemo Explained | React Hooks useMemo Tutorial

This video from Dave Gray clearly demonstrates the performance issue. You'll see a component with an expensive function and a simple text input. Notice the lag when typing into the input because the expensive function is being re-run unnecessarily.

Watch from 00:33 to 02:55. Pay close attention to the lag between typing in the 'Random Input' field and the text appearing on the screen. The narrator explains exactly why this happens: every state update from the input field is forcing the entire component to re-render, including the slow fib function.

This is the exact problem useMemo is designed to solve.


2. The Solution: Memoizing Values with useMemo

useMemo is a React Hook that lets you cache—or memoize—the result of a calculation. It will only recompute the memoized value when one of its dependencies has changed.

The syntax is as follows:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

It takes two arguments:

  1. A "creator" function that performs the calculation and returns the value you want to cache.
  2. A dependency array. React will only re-run the creator function if a value in this array has changed since the last render.

If the dependencies ([a, b]) haven't changed, React skips the function and returns the previously stored memoizedValue.

Now, let's see how to apply this to fix the laggy component from the video.

useMemo Explained | React Hooks useMemo Tutorial

Continuing with the same example, this part of the video shows how to wrap the expensive function call in useMemo to prevent it from running on every render.

Watch from 06:07 to 07:48. Notice how the call to the fib function is wrapped in useMemo. The userNumber state is placed in the dependency array, so the calculation will only re-run when userNumber changes. The result? The text input is fast and responsive again because the expensive calculation is no longer running every time you type.


3. Deeper Dive: When and Why to Use useMemo

useMemo is a powerful optimization tool, but it's important to understand it's not free. It uses memory to store the cached value and adds a small amount of overhead to compare dependencies on each render. Therefore, you should use it strategically.

The following article from the LogRocket Blog provides an excellent, pragmatic guide.

React useMemo vs. useCallback: A pragmatic guide - LogRocket Blog

This article, 'React useMemo vs. useCallback: A pragmatic guide', provides a great summary of what useMemo is, presents another practical example, and lists its pros and cons.

Please read the following sections: "What is useMemo?": This provides a concise definition. "A practical example for useMemo": This section walks through another code example of an expensive calculation causing a UI slowdown and shows how useMemo solves it. "Pros and cons of using useMemo": This table is very helpful for understanding when it's appropriate to use this hook. Don't overuse it!

The key takeaway is to use useMemo when a calculation is noticeably slowing down your app. Don't wrap every function call in useMemo—this is a common anti-pattern that can actually make performance worse due to the overhead.


4. An Equally Important Use Case: Referential Equality

Beyond pure performance, useMemo has another critical job: preserving referential equality for objects and arrays.

In JavaScript, non-primitive values (objects, arrays, functions) are compared by reference. This means that two objects or arrays are only considered equal (===) if they are the exact same instance in memory.

// Primitives are compared by value
console.log(1 === 1); // true

// Objects are compared by reference
console.log({} === {}); // false (two separate objects in memory)

const myObj = {};
console.log(myObj === myObj); // true (same object in memory)

Why does this matter in React? If you define an object or array inside your component, a new instance is created on every single render.

function MyComponent({ someProp }) {
  // A new `options` object is created on every render
  const options = { theme: 'dark', someProp };

  // If ChildComponent is wrapped in React.memo, it will still
  // re-render because `options` is a "new" object every time.
  return <ChildComponent options={options} />;
}

If you pass that object or array as a prop to a child component that is optimized with React.memo(), or as a dependency to a useEffect hook, React will see it as a "new" value on every render, triggering unnecessary re-renders or effect runs.

useMemo solves this by ensuring you get back the exact same object reference as long as its dependencies don't change.

function MyComponent({ someProp }) {
  // `options` is now the SAME object unless `someProp` changes
  const options = useMemo(() => ({
    theme: 'dark',
    someProp
  }), [someProp]);

  return <ChildComponent options={options} />;
}

The next clip from Dave Gray's video perfectly illustrates this problem and its solution.

useMemo Explained | React Hooks useMemo Tutorial

This final clip demonstrates how creating a new array on every render causes a useEffect to run unnecessarily, and how useMemo fixes it by providing a stable reference.

Watch from 09:48 to 12:44. The video introduces another 'expensive' function that returns an array. When this array is a dependency of useEffect, the effect runs on every single render. By wrapping the function call in useMemo, the returned array now has referential equality, and the useEffect only runs when it's supposed to.

This concept is subtle but fundamental to preventing needless re-renders in optimized React applications.


Conclusion

You've now learned how to use useMemo, a critical hook for optimizing your React applications. It's not just about speeding up slow functions; it's also a key tool for controlling when your components update by maintaining stable references for objects and arrays.

Key Takeaways:

  • useMemo caches (or memoizes) the return value of a function call.
  • It re-runs the calculation only when a value in its dependency array changes.
  • Use it for:
    1. Calculations that are demonstrably slowing down your component.
    2. Preserving referential equality for objects or arrays that are passed as props or used as hook dependencies.
  • Don't overuse it. For simple, fast calculations, the overhead of useMemo can outweigh the benefits. Always profile first if you suspect a performance issue.

Preview of the Next Lesson:

We'll conclude our tour of advanced hooks by looking at useCallback. It's very similar to useMemo, but with a specific purpose: it memoizes functions themselves, not their return values. This is the final piece of the puzzle for preventing unnecessary re-renders, especially when passing functions down to child components.