Skip to content

useMemo

useMemo caches a computed value and only recalculates it when its dependencies change. It is the performance tool for expensive derivations — when computing a value on every render is measurably slow.

import { useMemo } from 'react';
const spentByCategory = useMemo(() => {
return transactions.reduce((map, tx) => {
map[tx.categoryId] = (map[tx.categoryId] || 0) + Number(tx.amount || 0);
return map;
}, {});
}, [transactions]);

The first argument is a function that computes the value. The second is the dependency array — the same rules as useEffect. React calls the function on the first render and caches the result. On subsequent renders it returns the cached value unless transactions changed.

useMemo has a real cost: it allocates memory for the cache and runs a comparison on every render. For cheap computations, the overhead of useMemo is larger than just recomputing.

Use useMemo when:

  • The computation is visibly slow (you can measure it)
  • The result is used in a useEffect dependency or as a prop to a React.memo component
  • The calculation involves large datasets (thousands of items)

Do not use useMemo when:

  • The computation is a simple sum, map, or filter on a small array
  • You are applying it pre-emptively “just in case”
  • The value is only used inside the same component for rendering

ZeroBudget’s spentByCategory is a legitimate use: it iterates the full transactions list and the result is passed as a prop to every CategoryCard. With many transactions, memoizing prevents the loop from running on unrelated state changes.

The same rules as useEffect apply. Include every value the computation reads:

const filtered = useMemo(() => {
return transactions.filter(tx => tx.categoryId === selectedCategory);
}, [transactions, selectedCategory]); // both dependencies included

A missing dependency means the cached value is stale. An extra dependency means the cache invalidates more often than needed.

useMemo caches a value, not a render. Even if spentByCategory is memoized, the component that reads it still re-renders when other state changes. To prevent a component from re-rendering, use React.memo (covered in Lesson 04).

In useBudget.js:

const totalIncome = useMemo(
() => incomeSources.reduce((sum, s) => sum + Number(s.amount || 0), 0),
[incomeSources]
);
const totalBudgeted = useMemo(
() => categories.reduce((sum, c) => sum + Number(c.budgeted || 0), 0),
[categories]
);
const spentByCategory = useMemo(() => {
const map = {};
for (const t of transactions) {
map[t.categoryId] = (map[t.categoryId] || 0) + Number(t.amount || 0);
}
return map;
}, [transactions]);
  1. Add useMemo to your App.jsx (or useBudget.js) to memoize totalIncome from incomeSources.
  2. Add useMemo for spentByCategory from transactions.
  3. Add a console.log('recomputing spent') inside the useMemo callback and verify it only logs when transactions changes — not on unrelated state changes.
  4. Remove one dependency from the array and observe the stale value bug.
  • useMemo caches a computed value and only recalculates when dependencies change.
  • Use it for genuinely expensive computations or values used in dependency arrays.
  • Do not use it pre-emptively on simple calculations — the overhead is not worth it.
  • useMemo caches a value, not a render — use React.memo to prevent component re-renders.
  • Dependencies follow the same rules as useEffect — include everything the computation reads.