Back to Blog
React.js

React useMemo Hook: The Complete Performance Optimization Guide (2026)

19 July 20268 min read
React useMemo Hook: The Complete Performance Optimization Guide (2026)

Deep dive into React useMemo — covering memoization fundamentals, dependency arrays, comparison with useCallback, React.memo, and practical examples for preventing expensive re-computations.

useMemo is one of React's most important performance hooks — but also one of the most misused. Developers reach for it when they shouldn't, and avoid it when they should. This guide cuts through the confusion with a clear mental model, concrete examples, and the updated React 19 perspective on memoization.

What Is useMemo?

useMemo memoizes the result of a computation. It re-runs the computation only when one of its dependencies changes:

import { useMemo } from 'react';

function ProductList({ products, filterText, sortBy }) {
  const filteredAndSorted = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(filterText.toLowerCase()))
      .sort((a, b) => {
        if (sortBy === 'price') return a.price - b.price;
        return a.name.localeCompare(b.name);
      });
  }, [products, filterText, sortBy]);

  return (
    <ul>
      {filteredAndSorted.map(p => (
        <li key={p.id}>{p.name} — ₹{p.price}</li>
      ))}
    </ul>
  );
}

Without useMemo, the filter + sort operation runs on every render — even when neither products, filterText, nor sortBy changed. With it, React caches the result and only recomputes when a dependency actually changes.

The Signature

const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b), // factory function
  [a, b]                              // dependency array
);
  • The factory function runs during the first render

  • On subsequent renders, React compares the new dependencies to the previous ones using Object.is

  • If all dependencies are identical, it returns the cached value without calling the factory

  • If any dependency changed, it calls the factory and caches the new result

When TO Use useMemo

1. Expensive computations

// Finding prime numbers — genuinely expensive
const primes = useMemo(() => {
  return findPrimesUpTo(limit); // could take 50–200ms
}, [limit]);

Rule of thumb: If the computation takes more than 1ms (measure it with console.time), consider memoization.

2. Stable object/array references for child component props

// Without useMemo, this object is recreated on every render
// causing Chart to re-render even when data hasn't changed
const chartOptions = useMemo(() => ({
  responsive: true,
  plugins: { legend: { position: 'top' } },
  scales: { x: { type: 'linear' } },
}), []); // empty deps — object never changes

return <Chart data={data} options={chartOptions} />;

3. Derived state from large datasets

const stats = useMemo(() => {
  const total = transactions.reduce((sum, t) => sum + t.amount, 0);
  const average = total / transactions.length;
  const max = Math.max(...transactions.map(t => t.amount));
  return { total, average, max };
}, [transactions]);

When NOT to Use useMemo

This is where most developers go wrong. useMemo has overhead — it stores the cached value in memory and runs a dependency comparison on every render. For cheap computations, this overhead exceeds the savings.

// ❌ DON'T — this is faster without useMemo
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);

// ✅ DO — just compute it
const fullName = `${firstName} ${lastName}`;

// ❌ DON'T — primitive values don't benefit
const count = useMemo(() => items.length, [items]);

// ✅ DO
const count = items.length;

useMemo vs useCallback

The confusion between these two is common:

// useMemo — memoizes a VALUE (the result of calling the function)
const sortedList = useMemo(() => [...items].sort(), [items]);

// useCallback — memoizes a FUNCTION (the function reference itself)
const handleSort = useCallback(() => {
  setItems(prev => [...prev].sort());
}, []); // stable reference across renders

Mental model: useMemo(() => fn(), deps) is equivalent to useCallback(fn, deps)(). Use useCallback when you need to pass a stable function reference to a child component or useEffect. Use useMemo when you need a cached computed value.

useMemo with React.memo

useMemo is most impactful when combined with React.memo for child components:

// React.memo prevents re-render if props haven't changed
const DataTable = React.memo(({ rows, columns }) => {
  return (
    <table>
      {rows.map(row => (
        <tr key={row.id}>
          {columns.map(col => (
            <td key={col.key}>{row[col.key]}</td>
          ))}
        </tr>
      ))}
    </table>
  );
});

function Dashboard({ rawData, config }) {
  // Without useMemo, rows and columns are new arrays every render
  // React.memo won't help because props always look "different"
  const rows = useMemo(() => transformRows(rawData), [rawData]);
  const columns = useMemo(() => buildColumns(config), [config]);

  return <DataTable rows={rows} columns={columns} />;
}

The Dependency Array: Common Mistakes

Missing dependencies

// ❌ BUG — multiplier is used but not in deps
// This will use a stale multiplier value
const total = useMemo(() => {
  return items.reduce((sum, item) => sum + item.price * multiplier, 0);
}, [items]); // missing: multiplier

// ✅ CORRECT
const total = useMemo(() => {
  return items.reduce((sum, item) => sum + item.price * multiplier, 0);
}, [items, multiplier]);

Object dependencies (always "changed")

// ❌ BUG — filters object is new reference every render
// useMemo never caches because {} !== {} in JS
function SearchResults({ query }) {
  const filters = { query, page: 1 }; // new object each render
  const results = useMemo(() => search(filters), [filters]); // useless!

// ✅ CORRECT — use primitive dependencies
  const results = useMemo(() => search({ query, page: 1 }), [query]);
}

React 19: Do We Still Need useMemo?

React 19 introduced the React Compiler (previously React Forget) which automatically adds memoization where beneficial. When the compiler is enabled, you'll need manual useMemo calls significantly less often.

However:

  • The React Compiler isn't universally available yet — many projects are on React 18

  • The compiler can't detect all memoization opportunities (external data, complex closures)

  • Understanding useMemo is still essential for debugging compiler output and edge cases

How to Profile Whether useMemo Actually Helps

Never add useMemo speculatively. Measure first:

// Quick measurement technique
const start = performance.now();
const result = expensiveComputation(input);
console.log('Computation took:', performance.now() - start, 'ms');

// If > 1ms and called frequently → useMemo is worth it
// If < 0.1ms → don't bother

Use React DevTools Profiler to identify which components are re-rendering unnecessarily. That tells you where useMemo + React.memo combinations will have the most impact.

Quick Reference

Situation

Use useMemo?

Filter/sort large array (>1000 items)

Yes

String concatenation

No

Stable object prop for React.memo child

Yes

Primitive arithmetic (<100 operations)

No

Cryptographic or statistical computations

Yes

Derived state from large dataset

Yes

Memoizing a function reference

Use useCallback instead

Conclusion

React's useMemo is a targeted performance tool, not a blanket optimisation. Use it when computations are measurably expensive, when you need stable object references for memoized children, or when deriving complex state from large datasets. Skip it for simple expressions, primitives, and anywhere the React Compiler already handles it. Profile first, optimise second — always.

Was this article helpful?

C

Written by

CoderCrafter Team

Developer & Technical Writer

Building developer tools and writing practical engineering content at CoderCrafter. Covers JavaScript, React, Node.js, DevOps, and modern web development.

Comments

Leave a comment

0/2000

Comments are reviewed before appearing. Be kind and constructive.

Call UsWhatsApp