Skip to main content

Welcome to a Grasp lesson

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

Lesson 59

Memoization in React: useMemo vs. useCallback

Recommended resources

Introduction

Welcome! In our last lesson, we explored useEffect and established a critical concept: when a dependency is an object, array, or function, React checks for changes using reference equality. This means that if a new object or function is created on every render—even if its contents are identical—React sees it as a "change" and will re-run the effect. This same principle also causes child components to re-render unnecessarily.

This lesson provides the solution to that very problem. We will focus on two specific hooks, useMemo and useCallback, which are React's primary tools for performance optimization through memoization.

Our learning outcome is to differentiate between useMemo and useCallback and describe when to use them for performance optimization. By the end of this lesson, you will understand:

  • The core problem that useMemo and useCallback solve.
  • How useMemo memoizes values, both for expensive computations and for stabilizing object/array references.
  • How useCallback memoizes functions to stabilize their references.
  • The crucial relationship between these hooks and React.memo.
  • A practical, expert-level perspective on when—and when not—to use them.

Part 1: The Problem - Re-renders and Referential Equality

Before we can optimize, we need to be crystal clear on what we're optimizing for. By default, when a React component re-renders (due to a state or prop change), it also re-renders all of its children. This is usually very fast and not a problem.

However, sometimes we have a component that is expensive to render, and we want to prevent it from re-rendering if its props haven't actually changed. React's solution for this is React.memo. It's a higher-order component that wraps your component and performs a shallow comparison of its props. If the props are the same as the last render, React skips re-rendering the component.

This works perfectly for primitive props (strings, numbers). But it breaks down with reference types (objects, functions).

Let's watch a video that excellently demonstrates this foundational concept.

React Memo | useMemo | useCallback - Optimize Performance of your React Applications

The video 'React Memo | useMemo | useCallback' by Justin Kim clearly explains why components re-render and how React.memo can be broken by referential inequality.

Please watch the following two segments: Why components re-render (01:34 - 03:39): Pay attention to the third reason for a re-render: when a parent re-renders. This is the default behavior we often want to prevent. Using React.memo (06:43 - 11:20): Observe how React.memo successfully prevents re-renders with primitive props, but fails as soon as an inline function (a non-primitive) is passed as a prop. This sets the stage perfectly for useCallback.

The key takeaway is that even if a function or object prop is visually identical between renders, it's a new instance in memory. React.memo sees a new reference, concludes the props have changed, and re-renders the child, defeating the optimization.


Part 2: Memoizing Functions with useCallback

As you saw in the video, passing a newly defined function to a memoized child component breaks the memoization. The useCallback hook is the direct solution to this problem.

useCallback returns a memoized version of a function that only changes if one of its dependencies has changed. This allows you to pass a stable function reference to child components, preserving the optimization benefits of React.memo.

The syntax is:
const memoizedCallback = useCallback(callbackFunction, [dependencies]);

Let's continue with the video you just watched to see useCallback in action.

React Memo | useMemo | useCallback - Optimize Performance of your React Applications

This segment of Justin Kim's video demonstrates how to apply useCallback to fix the exact problem introduced in the previous clip.

Please watch from 11:20 to 17:57. Focus on: How wrapping the callback function in useCallback provides a stable reference, making React.memo work as expected. The critical importance of the dependency array. Notice the bug that occurs when the counter dependency is omitted and why it happens (stale closure).

By wrapping the updateCounterFromChild function in useCallback, its reference is preserved across re-renders of the parent component. React.memo now correctly sees that the onClick prop has not changed, and it skips the unnecessary re-render.


Part 3: Memoizing Values with useMemo

useCallback is specifically for functions. What about other non-primitive values like objects and arrays? Or what if we have a calculation inside our component that is computationally expensive and we don't want to re-run it on every single render?

This is where useMemo comes in.

useMemo returns a memoized value that is the result of a function call. It re-runs the function and re-computes the value only when one of its dependencies has changed.

The syntax is:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

There are two primary use cases for useMemo. The following article by Josh W. Comeau provides an excellent, in-depth explanation of both.

Understanding useMemo and useCallback

The article 'Understanding useMemo and useCallback' is a fantastic resource that aligns with your goal of deep conceptual understanding. We'll read it in parts.

Please read the following two sections: 'Use case 1: Heavy computations': This section demonstrates how useMemo can prevent a computationally intensive function (like finding prime numbers) from running on every render when its dependencies haven't changed. 'Use case 2: Preserved references': This part explains how useMemo can solve the same referential equality problem we saw with useCallback, but for objects and arrays. It shows how wrapping an array definition in useMemo prevents a memoized child component from re-rendering unnecessarily.

To summarize the two use cases:

  1. Expensive Computations: If a component re-renders due to an unrelated state change (like a clock ticking), useMemo prevents a costly calculation from being wastefully re-executed.
  2. Referential Stability: Just like useCallback for functions, useMemo provides a stable reference for objects and arrays, allowing React.memo to work correctly.

Part 4: Differentiating useMemo and useCallback

At this point, you might notice a strong similarity between the two hooks. That's because they are fundamentally related.

The simplest way to put it is:

  • useMemo memoizes a value.
  • useCallback memoizes a function.

In fact, useCallback is just syntactic sugar over useMemo. The following two lines of code are equivalent:

// This...
const memoizedFn = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

// ...is equivalent to this:
const memoizedFn = useMemo(() => {
  return () => {
    doSomething(a, b);
  };
}, [a, b]);

useCallback(fn, deps) is simply a more convenient and readable way of writing useMemo(() => fn, deps).

useMemo vs useCallback Comparison
This image provides a side-by-side code comparison, illustrating the syntax and typical use cases for `useMemo` (memoizing a computed value) and `useCallback` (memoizing a function).

Part 5: An Expert's Perspective - When to Optimize

You now understand what these hooks do and how they work. The final, and arguably most important, question is when to use them. As an experienced developer, you know that premature optimization is a common anti-pattern. These hooks are not free; they add complexity and consume memory.

A common misconception is that useMemo or useCallback on a prop will, by itself, prevent a child component from re-rendering. This is incorrect. These hooks are only effective for preventing re-renders when the child component they are passed to is wrapped in React.memo.

The following article provides a sharp, insightful perspective on this topic.

How to useMemo and useCallback: you can remove most of them

The article 'How to useMemo and useCallback: you can remove most of them' offers a critical and practical view, arguing that these hooks are often overused. This perspective is invaluable for developing a mature optimization strategy.

Please read these two sections: 'Why a component can re-render itself?': This section drives home the crucial point that a parent's re-render triggers its children's re-renders by default, and that useCallback/useMemo are only useful for breaking this chain when paired with React.memo. 'Avoiding expensive calculations on every render': This section challenges the notion of what constitutes an 'expensive' calculation. It demonstrates through performance measurements that most JavaScript operations are incredibly fast, and the real bottleneck is often the rendering of the component tree itself.

Based on this, here are some professional guidelines for using these hooks:

  1. Don't Optimize Prematurely: Don't wrap every function and object in these hooks by default. React is very fast. Only apply these optimizations in response to an actual, identified performance issue (e.g., using the React Profiler).
  2. The React.memo Connection: The primary use case for useCallback and useMemo (for referential stability) is to make React.memo work effectively on child components that receive non-primitive props. If the child isn't memoized, memoizing its props is often pointless.
  3. Truly Expensive Calculations: Use useMemo for calculations that are genuinely slow and are being re-run unnecessarily. As you read, simple array filtering/mapping is usually not "expensive." Calculating thousands of prime numbers, however, is.
  4. Stable APIs: It's good practice to use useMemo and useCallback when creating custom hooks or context providers. This ensures you provide a stable API to consumers, who may be relying on referential equality for their own optimizations.

Conclusion

You've now moved beyond just knowing the syntax of useMemo and useCallback to understanding their deep-seated purpose in React's rendering model.

Key Takeaways:

  • Core Purpose: Both hooks are for memoization. They help optimize performance by preserving a stable reference for non-primitives across re-renders (useCallback for functions, useMemo for objects/arrays) or by caching the result of an expensive calculation (useMemo).
  • The React.memo Partnership: The most significant use case for useCallback and useMemo is to enable React.memo to correctly prevent re-renders of child components that receive functions or objects as props.
  • useCallback vs. useMemo: useCallback(fn, deps) is syntactic sugar for useMemo(() => fn, deps). The former memoizes the function itself, while the latter memoizes the return value of a function.
  • Optimization Strategy: These are not tools to be used indiscriminately. They should be applied strategically to solve diagnosed performance problems, not as a default coding practice.

What's Next?

We've focused on hooks that help manage state and re-renders. However, sometimes you need to interact with the DOM directly or hold a value that can change without triggering a re-render. In our next lesson, we will explore useRef, a hook that provides a powerful way to "escape" the typical React state and render cycle for specific use cases.