useMemo vs useCallback: 5 Mistakes That Slow Down Your React App

Understanding the essence of memoization is the foundation for avoiding common pitfalls and building a cleaner, more maintainable codebase. This article takes a close look at how the two hooks work, how they differ, and, more importantly, the situations where you absolutely should not use them.

useMemouseCallbacktối ưu Reactmemoizationhook
Cover image: useMemo vs useCallback: 5 Mistakes That Slow Down Your React App
Avatar of Trung Vũ Hoàng

Trung Vũ Hoàng

Author

22/3/20265 min read

1. What Is Memoization in React?

One thing to understand first: every operation in React has a cost. Each time a component re-renders, all variables and functions inside it are re-created from scratch.

Memoization is a technique that caches computed results or function definitions. When the inputs (dependencies) haven't changed, React returns the cached data instead of recalculating—saving CPU resources.

However, caching requires memory and time to compare dependencies on each render. Premature, thoughtless optimization often does more harm than good.

2. useMemo: Memoizing Computation Results

useMemo is a hook that caches a function's return value—especially useful when that function performs expensive computations.

How It Works

On the initial render, the function passed to useMemo runs as usual. From the second render onward, React checks the dependency array:

  • If a dependency changes: The function runs again to produce a new result.

  • If dependencies are unchanged: React returns the previously cached result—no work is redone.

Real-World Example

You have a list of 5,000 products and need to filter by the user's search keyword. This is a heavy computation—but it only needs to run when the list or the query actually changes:

const filteredProducts = useMemo(
  () => filterLogic(products, query),
  [products, query]
);

3. useCallback: Memoize Function Definitions

While useMemo caches a value, useCallback caches the function you pass in—it doesn't execute the function, it just remembers it.

Why Memoize a Function?

In JavaScript, functions are objects. Whenever a component re-renders, functions declared inside it receive a new reference in memory—even if their body doesn't change.

This becomes a problem when you pass that function to a child component wrapped in React.memo. Because the new function is not the same reference as the old one, the child component re-renders needlessly.

When Does useCallback Actually Help?

  • When passing a function as a prop to a child component wrapped with React.memo.

  • When the function is a dependency of another hook such as useEffect.

4. Key Differences

Purpose

  • useMemo: Cache a value—number, string, array, object...

  • useCallback: Cache a function (function reference).

Return value

  • useMemo: The result after the callback has executed.

  • useCallback: The callback function itself, not executed.

Syntax relationship

In fact, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

5. Five Common Memoization Mistakes

The habit of "wrapping everything in useMemo and useCallback" is a common anti-pattern. Here are practical reasons to stop:

Mistake 1: Dependency comparison costs outweigh the benefits

Comparing the dependency array on every render can cost more than recreating a simple function. Memoization only pays off when the work is truly expensive.

Mistake 2: Making code harder to read and maintain

Overusing these hooks turns a component into a maze of logic and dependencies. Teammates won't know where to start.

Mistake 3: Causing stale closures—the hardest bugs to track down

If you forget to include a variable in the dependency array, the function will read stale data—leading to silent logic bugs with no errors in the console.

Mistake 4: Applying it to primitive values

Types like number, string, and boolean are compared by value. Wrapping them in useMemo is completely redundant.

Mistake 5: Optimizing before measuring

Don't add memoization because you think something might be slow. Use React DevTools Profiler to identify real bottlenecks before intervening.

6. A Sound Optimization Strategy

A practical workflow I use in production projects:

  1. Step 1: Write code naturally, without thinking about optimization.

  2. Step 2: Use React DevTools Profiler to find components that re-render the most.

  3. Step 3: Check whether those re-renders actually cause user-perceived lag. If not, ignore them.

  4. Step 4: If optimization is needed, try refactoring the component (composition pattern) first.

  5. Step 5: Only when necessary, use useMemo for heavy computations and useCallback for functions passed to children.

7. Summary

useMemo and useCallback are powerful when used in the right places, but unnecessary baggage when overused. Remember:

  • Use useMemo to protect the results of expensive calculations.

  • Use useCallback to keep function references stable when passing to children.

  • Measure first, optimize later.

Have you ever added useMemo and found the app got slower? That's usually a sign of mistake #1 or #3 above!

Found this article helpful?

Contact us for a free consultation about our services

Contact us

Bài viết liên quan