Skip to content

Derived State

Derived state is any value you can compute from existing state. It does not need its own useState. Computing it during render keeps your state minimal and eliminates entire categories of bugs.

Suppose you store both the income sources array and the total income in state:

const [incomeSources, setIncomeSources] = useState([]);
const [totalIncome, setTotalIncome] = useState(0);

Every time you add or remove a source, you have to update both. If you forget one — or update them in the wrong order — the total is wrong. Two pieces of state that always have to agree is a bug waiting to happen.

The total is always the sum of the sources. Compute it:

const [incomeSources, setIncomeSources] = useState([]);
const totalIncome = incomeSources.reduce((sum, s) => sum + Number(s.amount || 0), 0);

totalIncome is recalculated every time incomeSources changes. It is always correct. No useEffect, no setter, no synchronization needed.

Several values in ZeroBudget are derived:

// Total of all income sources
const totalIncome = incomeSources.reduce((sum, s) => sum + Number(s.amount || 0), 0);
// Total budgeted across all categories
const totalBudgeted = categories.reduce((sum, c) => sum + Number(c.budgeted || 0), 0);
// Left to assign — the hero number
const leftToAssign = totalIncome - totalBudgeted;
// Spent per category — a map from categoryId to total spent
const spentByCategory = transactions.reduce((map, tx) => {
map[tx.categoryId] = (map[tx.categoryId] || 0) + Number(tx.amount || 0);
return map;
}, {});
// Inside each CategoryCard — also derived
const spent = spentByCategory[category.id] || 0;
const remaining = category.budgeted - spent;
const pct = category.budgeted > 0 ? Math.min((spent / category.budgeted) * 100, 100) : 0;

None of these live in useState. They are computed fresh on every render from the arrays that do live in state.

For most apps, re-computing derived values on every render is fast. JavaScript can sum 1,000 numbers in microseconds. The browser renders at 60fps — you have 16ms per frame. Simple calculations almost never cause performance problems.

When they do — because the list is enormous or the computation is expensive — useMemo caches the result. That is covered in the next lesson.

Store the minimum state needed to describe your UI. Derive everything else. Only add a new useState when a value truly cannot be computed from existing state.

  1. In App.jsx, remove any totalIncome state variable you might have added. Compute it from incomeSources with reduce directly in the component body.
  2. Compute totalBudgeted from the categories array.
  3. Compute leftToAssign = totalIncome - totalBudgeted and pass it to LeftToAssign.
  4. Build the spentByCategory map from transactions and pass it to each CategoryCard. In CategoryCard, compute spent, remaining, and pct from it.
  • Derived state is any value computable from existing state — do not store it separately.
  • Compute derived values during render with reduce, arithmetic, and other pure functions.
  • Storing derivable values creates synchronization bugs — two pieces of state that must always agree.
  • For expensive computations on large datasets, useMemo caches the result (next lesson).
  • The rule: minimum state, maximum derivation.