Featured image of post Stop Overusing useMemo and useCallback in React Featured image of post Stop Overusing useMemo and useCallback in React

Stop Overusing useMemo and useCallback in React

Analyze the hidden performance overhead of useMemo and useCallback in React, and establish guidelines for when to apply memoization.

Introduction

When optimizing React application performance, developers often reach for useMemo and useCallback.

It is tempting to wrap every object and function in these hooks, assuming that caching outputs will automatically speed up the application. However, this is a common misconception.

Unnecessary memoization does not just make code harder to read; it adds performance overhead from shallow dependency comparisons and garbage collection memory allocations.

This article outlines the specific scenarios where these hooks are beneficial and when they should be avoided.


1. The Hidden Costs of Memoization

Neither useMemo nor useCallback are free operations. They incur runtime costs during every render cycle:

  1. Dependency Comparison Overhead: On every render, React must shallow-compare (using Object.is) every value inside the dependency array. Executing these array checks adds CPU overhead.
  2. Memory Retention: Keeping references to previous functions or computed values prevents them from being garbage-collected, increasing the application’s memory footprint.
  3. Complexity and Bugs: Incorrectly configured dependency arrays can lead to stale closures, where functions reference outdated state or props.

2. Guidelines for Using useMemo

Use useMemo only in these two scenarios:

Scenario A: Computationally Expensive Tasks

Use it for CPU-intensive operations such as sorting large arrays, filtering complex datasets, or running heavy mapping operations.

  • Recommended Usecase:
    // Memoize search calculations on large lists
    const filteredItems = useMemo(() => {
      return heavySearch(items, query);
    }, [items, query]);
    

Scenario B: Preserving Object References for React.memo

React reinstantiates object declarations inside components on every render, changing their memory address.

If an object is passed as a prop to a child component optimized with React.memo, the child will re-render anyway because the object reference changed. Use useMemo to keep the reference stable.

  • Recommended Usecase:
    // Stabilize the options object reference
    const options = useMemo(() => ({ color: themeColor }), [themeColor]);
    
    // Sibling components optimized with React.memo will not re-render unnecessarily
    return <HeavyChildComponent options={options} />;
    

3. Guidelines for Using useCallback

The purpose of useCallback is not to prevent function reinstantiation, but to preserve the function’s memory reference across renders.

Only apply useCallback in this scenario:

Passing Callbacks to Memoized Child Components

If a parent component re-renders, any functions declared inside it are recreated, changing their references. If these callbacks are passed as props to a child component wrapped in React.memo, the child will re-render regardless of the memoization.

Use useCallback to keep the reference stable.

  • Recommended Usecase:

    // Stabilize click callback references
    const handleClick = useCallback(() => {
      console.log("Button clicked!");
    }, []);
    
    // Prevents re-renders if MemorizedButton uses React.memo
    return <MemorizedButton onClick={handleClick} />;
    
  • Important Note: If the child component is not wrapped in React.memo, it will re-render when the parent does, regardless of whether you use useCallback. In this case, the hook adds unnecessary overhead.


Conclusion: React 19 and the React Compiler

With the release of React 19, the React Compiler (React Forget) handles memoization automatically at build time. This reduces the need to write useMemo and useCallback manually.

When optimizing manually:

  1. Profile first to verify that a calculation is actually causing rendering delays.
  2. Avoid wrapping callbacks unless the target child component uses React.memo.
  3. Prioritize writing clean, simple JavaScript logic over preemptive optimization.

Following these guidelines keeps your React code readable and performant.