useMemo Hook ⚡

1. Introduction 👋

useMemo is a performance-focused Hook that lets you cache the result of an expensive calculation between renders. This tutorial covers when and how to use useMemo effectively, how it differs from related Hooks like useCallback and React.memo, and when it's better left out entirely.

Information

This tutorial assumes familiarity with useState and Rendering Lists, covered in earlier tutorials.

2. What is useMemo? 🤔

useMemo is a Hook that memoizes the result of a calculation, recomputing it only when its dependencies change — instead of on every single render.

Code Snippet

import { useMemo } from 'react';

function ProductList({ products, searchTerm }) {
  const filteredProducts = useMemo(
    () => products.filter((p) => p.name.includes(searchTerm)),
    [products, searchTerm]
  );

  return <ul>{filteredProducts.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
>>"useMemo is a React Hook that lets you cache the result of a calculation between re-renders." — React Documentation

3. Why Use useMemo? 💡

  • Avoid Redundant Work: Skip expensive calculations when their inputs haven't changed.
  • Stable References: Prevent unnecessary re-renders in memoized child components that receive computed objects or arrays.
  • Smoother UI: Reduce jank in components with computationally heavy rendering logic.

Caution

useMemo is a performance optimization, not a correctness tool — your component should work correctly with or without it.

4. Memoization 🧠

Memoization is a general programming technique where a function's result is cached based on its inputs, so repeated calls with the same inputs return the cached result instead of recomputing.

Code Snippet

// Without memoization: recalculates every single render
const total = calculateTotal(items);

// With memoization: recalculates only when 'items' changes
const total = useMemo(() => calculateTotal(items), [items]);

5. How useMemo Works âš™ī¸

On the first render, React runs the function and stores its result along with the current dependency values. On subsequent renders, React compares the new dependencies against the stored ones — if they match, it returns the cached result without re-running the function.

Component Re-renders
React Checks Dependencies
Unchanged → Return Cached Value
Changed → Re-run Function, Cache New Result

6. Syntax 📐

Code Snippet

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • The first argument is a function that returns the value to memoize.
  • The second argument is the dependency array controlling when to recompute.

Important

The function passed to useMemo must be pure — it should compute a value without producing side effects like API calls or state updates.

7. Dependency Array 📋

React recomputes the memoized value only when at least one value in the dependency array has changed since the last render, compared using Object.is().

Code Snippet

const sortedItems = useMemo(
  () => [...items].sort((a, b) => a.price - b.price),
  [items] // recomputes only when 'items' changes
);

Note

An empty array [] means the value is computed once and never recalculated for the component's lifetime.

8. Memoizing Expensive Calculations 🧮

Code Snippet

function Dashboard({ transactions }) {
  const summary = useMemo(() => {
    console.log("Recalculating summary..."); // only logs when 'transactions' changes
    return transactions.reduce(
      (acc, t) => ({
        total: acc.total + t.amount,
        count: acc.count + 1,
      }),
      { total: 0, count: 0 }
    );
  }, [transactions]);

  return <p>Total: ${summary.total} across {summary.count} transactions</p>;
}

Tip

Calculations involving large datasets, complex sorting, filtering, or aggregation are prime candidates for useMemo.

9. Memoizing Derived Values 🔗

Code Snippet

function OrderSummary({ items, taxRate }) {
  const subtotal = useMemo(
    () => items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    [items]
  );

  const total = useMemo(() => subtotal * (1 + taxRate), [subtotal, taxRate]);

  return <p>Total: ${total.toFixed(2)}</p>;
}

Note

Memoized values can depend on other memoized values, forming a chain that only recalculates the necessary parts when their specific inputs change.

10. Memoizing Objects 🧱

Memoizing an object prevents a new reference from being created on every render, which is especially useful when passing that object as a prop to a memoized child component.

Code Snippet

function Chart({ data, color }) {
  const chartConfig = useMemo(
    () => ({ data, color, animation: true, responsive: true }),
    [data, color]
  );

  return <ThirdPartyChart config={chartConfig} />;
}

11. Memoizing Arrays 📚

Code Snippet

function ProductGrid({ products, category }) {
  const filteredProducts = useMemo(
    () => products.filter((p) => p.category === category),
    [products, category]
  );

  return (
    <div className="grid">
      {filteredProducts.map((p) => <ProductCard key={p.id} product={p} />)}
    </div>
  );
}

Tip

Memoizing filtered or sorted arrays is especially valuable when the result is passed to a memo-wrapped list component, avoiding re-renders triggered by a fresh array reference.

12. Preventing Unnecessary Computations đŸšĢ

Without useMemo — Recalculates on Every Render

function SearchResults({ items, query, unrelatedState }) {
  // Recalculates every time ANY state changes, even unrelated state
  const results = items.filter((item) => item.name.includes(query));
  // ...
}

With useMemo — Only Recalculates When Needed

function SearchResults({ items, query, unrelatedState }) {
  const results = useMemo(
    () => items.filter((item) => item.name.includes(query)),
    [items, query] // unaffected by 'unrelatedState' changes
  );
}

13. useMemo vs useCallback âš”ī¸

AspectuseMemouseCallback
MemoizesThe result of a function callThe function itself
Typical UseExpensive computed valuesStable callback references for child props

Code Snippet

// useMemo: caches the computed VALUE
const total = useMemo(() => calculateTotal(items), [items]);

// useCallback: caches the FUNCTION itself
const handleClick = useCallback(() => addItem(id), [id]);

// useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)

14. useMemo vs React.memo âš”ī¸

AspectuseMemoReact.memo
Applies ToA value computed inside a componentAn entire component
PreventsRecalculating a value unnecessarilyRe-rendering when props are unchanged

Note

The two are often used together: useMemo stabilizes a prop's reference, while React.memo on the receiving component skips re-rendering when that stable reference doesn't change.

15. useMemo vs useRef âš”ī¸

AspectuseMemouseRef
PurposeCache a computed value based on dependenciesPersist a mutable value with no automatic recomputation
RecalculationAutomatic, based on dependency arrayManual — you control when .current changes

Caution

useMemo does not guarantee the cached value survives forever — React may discard it under memory pressure and recompute it, so never rely on it for correctness like you would a ref.

16. Performance Optimization ⚡

useMemo trades extra memory usage for avoided computation. It's most valuable when a calculation is genuinely expensive relative to the cost of the comparison check itself.

  • Large array transformations (sorting, filtering, mapping over thousands of items).
  • Complex mathematical or statistical computations.
  • Stabilizing object/array references passed to memoized children.

Warning

For simple, cheap calculations, the overhead of useMemo's dependency comparison can actually be slower than just recalculating the value directly.

17. Dependency Management 📐

Code Snippet

function Component({ items, threshold }) {
  const filtered = useMemo(
    () => items.filter((item) => item.value > threshold),
    [items, threshold] // include every value read inside the function
  );
}

Important

Just like useEffect, omitting a dependency that's actually used inside the memoized function creates a stale value bug — the result silently falls out of sync with its inputs.

18. Stale Values đŸ•°ī¸

❌ Stale Value — Missing Dependency

function Cart({ items, discountRate }) {
  const total = useMemo(
    () => items.reduce((sum, i) => sum + i.price, 0) * (1 - discountRate),
    [items] // ❌ missing 'discountRate' — total won't update when discount changes
  );
}

✅ Correct Dependencies

function Cart({ items, discountRate }) {
  const total = useMemo(
    () => items.reduce((sum, i) => sum + i.price, 0) * (1 - discountRate),
    [items, discountRate] // ✅ all inputs included
  );
}

Tip

Enable eslint-plugin-react-hooks's exhaustive-deps rule to catch missing dependencies in useMemo automatically.

19. Common Use Cases 🎨

  • Filtering and sorting large lists based on user input.
  • Aggregating data for charts, dashboards, or summaries.
  • Stabilizing props passed into memoized child components.
  • Expensive formatting, like generating complex derived strings or structures.

20. When Not to Use useMemo đŸšĢ

  • For cheap calculations (simple arithmetic, string concatenation, small array operations).
  • When the value doesn't get passed to a memo-wrapped component or used in another Hook's dependency array.
  • As a default habit applied to every computed value "just in case."

Best Practice

Write your component without useMemo first. Add it only after identifying an actual performance bottleneck, ideally confirmed with the React DevTools Profiler.

21. TypeScript with useMemo 🔷

Code Snippet

interface Product {
  id: string;
  name: string;
  price: number;
}

function ProductList({ products, query }: { products: Product[]; query: string }) {
  const filtered: Product[] = useMemo(
    () => products.filter((p) => p.name.includes(query)),
    [products, query]
  );

  return <ul>{filtered.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}

Tip

TS automatically infers the memoized value's type from the function's return type — explicit generics are rarely necessary with useMemo.

22. Best Practices 🌟

  1. Reserve useMemo for genuinely expensive calculations, not trivial ones.
  2. Always include every value used inside the function in the dependency array.
  3. Combine with React.memo when stabilizing props for a memoized child component.
  4. Profile before optimizing — confirm a real bottleneck exists before reaching for useMemo.
  5. Keep the memoized function pure, with no side effects.

23. Common Mistakes đŸšĢ

  • Wrapping every value in useMemo, even trivial ones, adding unnecessary overhead.
  • Omitting dependencies, leading to stale, out-of-sync memoized values.
  • Treating useMemo as a guarantee rather than an optimization hint React may discard.
  • Performing side effects (like API calls) inside the memoized function.
  • Using useMemo when the underlying issue is actually an unnecessary re-render higher up the component tree.

Danger

Because React doesn't guarantee memoized values persist indefinitely, code that relies on useMemo to skip essential side effects (rather than pure recalculation) can behave unpredictably.

24. Frequently Asked Questions ❓

Question

Does useMemo make my component render faster?

Answer

It skips recalculating a specific value, not the render itself. The component still re-renders normally; only the wrapped computation is potentially skipped.

Question

Is it safe to rely on useMemo for correctness?

Answer

No. useMemo is purely a performance hint — React may discard the cache and recompute the value at any time, so your component must work correctly either way.

Question

Should I use useMemo for every derived value?

Answer

No. Most derived values are cheap enough to recalculate on every render — reserve useMemo for calculations that are measurably expensive.

25. Summary 📝

useMemo caches the result of a computation between renders, recalculating only when its dependencies change. It's a valuable tool for expensive calculations and stabilizing object or array references — but should be applied deliberately, based on measured performance needs, rather than by default.

Summary

With useMemo covered, a natural next step is exploring useCallback in depth, along with React.memo, since these three tools are frequently combined to optimize component re-rendering.