Derived state: compute, don't store
A common beginner instinct is to create state for everything on screen. But if a value can be computed from existing state or props, it should not be state at all, just compute it during render:
function Cart({ items }) { // NOT state, plain consts computed on every render const subtotal = items.reduce((sum, it) => sum + it.price * it.qty, 0); const shipping = subtotal >= 100 ? 0 : 7; return <p>Total: {subtotal + shipping}</p>; }
reduce is the array-folding tool from Advanced JavaScript. Every render recomputes these consts from the freshest items, so they can never be out of date.
Storing them in state instead would mean updating TWO things on every cart change, and forgetting is the sync bug from lesson 1-1 all over again, this time inside React. The rule of thumb: state is only for facts React cannot compute, everything else is derived.
Derived values as pure functions
Subtotal folds price times quantity over the items, shipping derives from subtotal, and total derives from both.
const items = [ { name: "Keyboard", price: 89, qty: 1 }, { name: "Cable", price: 12, qty: 3 }, ]; const subtotal = items.reduce((sum, it) => sum + it.price * it.qty, 0); const shipping = subtotal >= 100 ? 0 : 7; const total = subtotal + shipping; console.log("subtotal: " + subtotal); console.log("shipping: " + shipping); console.log("total: " + total);
Output
subtotal: 125 shipping: 0 total: 125
The arithmetic is 89 times 1 plus 12 times 3, which is 125, and that clears the free-shipping threshold so shipping is 0. Drop the cable quantity to 1 and the subtotal becomes 101, still free, while dropping the keyboard entirely makes it 36 and shipping jumps to 7.
The chain is what makes this a good illustration, since shipping depends on subtotal and total depends on both. Storing any one of the three in state would mean the other two could disagree with it, and there is no update path that keeps three stored numbers consistent for free.
Order of declaration matters here in a way it would not for state. Each const reads the ones above it, so the sequence encodes the dependency graph, and the language enforces it by refusing to read a const before its initializer runs.
reduce with an initial value of 0 is the standard fold from Advanced JavaScript, and the initial value is not optional in practice. Omitting it on an empty cart throws instead of returning 0, which is exactly the case a cart hits first.
The derived values a task tracker renders
remaining(tasks) counts the tasks where done is false, and summary(tasks) turns that count into the sentence the UI shows.
const tasks = [ { title: "Water plants", done: true }, { title: "Pay rent", done: false }, { title: "Call mom", done: false }, ]; function remaining(tasks) { return tasks.filter(t => !t.done).length; } function summary(tasks) { const left = remaining(tasks); return left === 0 ? "All done!" : left + " task(s) left"; } console.log(summary(tasks)); console.log(summary(tasks.map(t => ({ ...t, done: true }))));
Output
2 task(s) left All done!
remaining filters for !t.done and takes the length, which is the shortest honest way to count matches. Counting with a loop and a manual tally does the same thing and gives you a variable to get wrong.
summary calls remaining once and stores the result, then branches on it. Calling remaining(tasks) twice inside the ternary would also work and would walk the array twice for one answer, and naming it makes the branch read as a sentence.
The second console.log marks every task done via map plus spread, which is the immutable update from lesson 4-3. It builds a new array of new objects, so the original tasks is untouched and the first result stays valid.
Neither function is state, and that is the point of the lesson. A task tracker stores the tasks array and computes the count and the sentence on every render, so the header can never disagree with the list beneath it.
Note that summary derives from remaining, which derives from tasks, forming a two-step chain from a single stored fact. Chains like this are cheap and are the normal shape of a component's body above its JSX.
A count stored beside the array
const [tasks, setTasks] = useState([]); const [taskCount, setTaskCount] = useState(0); function addTask(t) { setTasks([...tasks, t]); setTaskCount(taskCount + 1); }
taskCount duplicates information that tasks already contains, so the fix is to store only tasks and derive const taskCount = tasks.length;.
taskCount is always tasks.length, so keeping it separately creates a second source of truth that every future handler must remember to update. addTask happens to get it right, and the delete handler someone writes next month is where it breaks.
The failure mode is a number that disagrees with the list on screen, which users notice immediately and which no error message accompanies. It is also unfixable by looking at the component that renders the count, since the bug lives in whichever handler forgot the second setter.
| Approach | Handlers to touch when the list changes |
|---|---|
store tasks, derive count | 1 |
store tasks and taskCount | 2, and every new one forever |
Deleting the state is the whole fix, and it removes a line rather than adding one. Less state means fewer bugs, and the rule of thumb behind it is that state is only for facts React cannot compute, while everything else is derived.
Note that performance is not a reason to keep the stored copy. tasks.length is free, and even a reduce over a few thousand items is far cheaper than a re-render, so the optimization instinct here buys nothing and costs correctness.