Derive a running total during render instead of storing it
Canonical URL: https://devexamples.com/javascript/derive-a-running-total-in-react-state/
The problem
Storing a total beside the items that produce it creates two sources of truth, and every mutation path — add, remove, edit quantity — has to remember to update the second one.
Short answer
Delete the total state and compute it in render from the items array; reach for useMemo only when measurement shows that pass is the cost.
The bug is visible in the state declaration, not in the arithmetic.
import { useState } from 'react';
export function Cart({ initialItems = [] }) {
const [items, setItems] = useState(initialItems);
// No total state, no effect, no synchronisation problem.
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
function setQuantity(id, quantity) {
setItems((current) =>
current.map((item) => (item.id === id ? { ...item, quantity } : item)),
);
}
function remove(id) {
setItems((current) => current.filter((item) => item.id !== id));
}
return (
<>
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
<input
type="number"
min="0"
value={item.quantity}
onChange={(event) => setQuantity(item.id, Number(event.target.value))}
/>
<button type="button" onClick={() => remove(item.id)}>
Remove
</button>
</li>
))}
</ul>
<p aria-live="polite">Total: {total.toFixed(2)}</p>
</>
);
}// The version that needs fixing: a stored total chasing the list.
const [items, setItems] = useState(initialItems);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((sum, item) => sum + item.price * item.quantity, 0));
}, [items]);
// First render shows 0, then a second render shows the real total.Explanation
total is a pure function of items, so React already has everything needed to compute it while
rendering. Deriving it means any state update to items — from any handler, now or added later —
produces the correct total in the same render, because there is nothing to keep in sync. The
stored version cannot promise that: the effect runs after the browser has painted, so the first
pass shows the stale value and a second render follows it, which is where the flicker of a total
correcting itself comes from.
The effect variant is worse than merely late. Because the effect depends on items and calls
setTotal, the component renders twice per change, and any handler that reads total in the same
tick as a mutation — for example to disable a checkout button — sees the pre-update value. Derived
values computed in render are always current for the render they belong to.
useMemo is a separate question. Wrapping the reduce in a memo does not change correctness, only
the cost of recomputation, and for a cart of a dozen items the arithmetic is not measurable. Add
the memo when a real profile shows the pass is expensive — a long list, a sort plus a group, or a
parse — and keep the derived expression otherwise.
Assumptions
- React 19 function component with hooks; the fragment and
aria-liveparagraph are present so the announced total is accessible. - Prices are numbers in the smallest unit you intend to display. For currency arithmetic, multiply by a scale factor or use integer cents, because float addition drifts.
Common mistakes
- Copying a prop or a computed value into state so it can be “updated later”, then discovering the copy survives a data change.
- Recomputing inside
setStatecallbacks so the total is right but no longer a single source of truth.
Caveats
- A derived value recomputes on every render; if the input list is large, memoise the derivation itself rather than storing its result in state.
- If the total must be editable by the reader, it is no longer derived — it is genuine state, and the two should be named differently.
Alternatives
Storing the total makes reads free and every write a place to forget; deriving makes reads cheap arithmetic and removes the failure mode entirely. A memo boundary adds nothing until a measurement shows the arithmetic is the bottleneck.
Follow-up
Follow-up
Validate a controlled React form on submitForm state is the most common place a derived value is needed.
Examples that treat this as a prerequisite
Derived at build time from the editorial graph; no edge is invented here.
- Memoise a derived value with typed dependencies
Read the plain derived-during-render version first to judge whether a memo boundary is needed.
- Type a React context provider and its consumer
Decide which value belongs in state before choosing to share it through context.
Related examples
Editorial links first, then deterministic same-task or same-topic candidates. Tags and shared language alone never qualify a candidate.
Related
Memoise a derived value with typed dependenciesThe same derivation with an explicit memo boundary and typed inputs.
Related
Type a React context provider and its consumerA value many components need may belong in context rather than duplicated state.
Same task or topic
Validate a controlled React form on submitShares the React State topic
References
- You Might Not Need an Effect(opens in a new tab) — React Team. States that values computable from props and state should be derived during render.
Source page: https://devexamples.com/javascript/derive-a-running-total-in-react-state/