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.
The basic shape
Section titled “The basic shape”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.
When to use useMemo
Section titled “When to use useMemo”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
useEffectdependency or as a prop to aReact.memocomponent - 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 dependency trap
Section titled “The dependency trap”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 includedA missing dependency means the cached value is stale. An extra dependency means the cache invalidates more often than needed.
useMemo does not prevent re-renders
Section titled “useMemo does not prevent re-renders”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).
ZeroBudget usage
Section titled “ZeroBudget usage”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]);Exercise
Section titled “Exercise”- Add
useMemoto yourApp.jsx(oruseBudget.js) to memoizetotalIncomefromincomeSources. - Add
useMemoforspentByCategoryfromtransactions. - Add a
console.log('recomputing spent')inside theuseMemocallback and verify it only logs whentransactionschanges — not on unrelated state changes. - Remove one dependency from the array and observe the stale value bug.
useMemocaches 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.
useMemocaches a value, not a render — useReact.memoto prevent component re-renders.- Dependencies follow the same rules as
useEffect— include everything the computation reads.