Topic 6.2
Derived Values: Compute, Don't Store
In one line
If a value can be calculated from props or state, calculate it during render instead of storing it; copying a prop into state freezes it at its first value, which is fine only when the prop is meant as a starting value.
Think of it like this
A shopping bill. The shopkeeper doesn't keep a separate note saying "total: 240" and try to remember to change it each time you add a packet of biscuits. They add up the items on the bill whenever you ask. The items are the facts; the total is derived from them, so it can never be wrong.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Derived value
- A value calculated from props or state, such as a total or a filtered list. It isn't stored.
- During render
- In the body of your component function, each time React calls it.
- Two sources of truth
- The same fact kept in two places that must be kept equal by hand.
- Mirroring props
- Copying a prop into state with
useState(prop). The state ignores later changes to the prop. - Initial value
- The argument to
useState. React uses it only on the component's first render. - initialX / defaultX props
- A naming habit for props that are only starting values, so readers know changes are ignored.
- useMemo
- A hook that caches a calculated value until its inputs change (Topic 16.4).
Step by step
01List the facts and the derived values
In a to-do list with a filter, the facts are the to-dos and which filter is chosen. The visible to-dos, the number left and the number done are all derived. Only the facts go into useState.
02Calculate them in the component body
Plain constants, worked out from the current state each render. No setters, no syncing.
const [todos, setTodos] = useState(initialTodos);
const [filter, setFilter] = useState("all");
const visibleTodos = todos.filter((t) =>
filter === "all" ? true : filter === "done" ? t.done : !t.done
);
const leftCount = todos.filter((t) => !t.done).length;03See the mirror bug
A child copies its prop into state. On the first render they match. The parent changes the prop; React renders the child again with the new prop, but useState returns the value it already has. The initial value argument is ignored after the first render.
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor); // frozen at the first value
return <p style={{ color }}>Hello</p>;
}04Fix it by reading the prop
Delete the state. The prop is already the value you want, and it is always current.
function Message({ messageColor }) {
const color = messageColor; // or use messageColor directly
return <p style={{ color }}>Hello</p>;
}05Keep the copy only for starting values
When the component should own the value after it starts (the user edits it), copying is right. Name the prop to say so, and use a key if the parent ever needs a fresh start.
function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount); // starting value only: correct
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Try it yourself
- 1
Add a derived value
In the to-do example, add
const doneCount = todos.length - leftCount;and show{doneCount} done. Predict: do you need to touchtoggle? (No. Derived values follow the state automatically.) - 2
Store the count and watch it break
Replace
leftCountwithconst [leftCount, setLeftCount] = useState(2);and tick a box. The number never changes. Now try to fix it by callingsetLeftCountinsidetoggle: notice how you must repeat the counting logic. Delete it again and enjoy the shorter code. - 3
Fix the mirror
In the side-by-side example, delete the
useStateline inMirroredMessageand usecolordirectly. Both messages now agree.
Code & diagrams
Two pieces of state, three derived values. Tick a box while the `left` filter is on: the item leaves the list and the count drops in the same render, with no code to keep them in step.
Preview
Console
Nothing logged yet.Expected console output
render: 3 shown, 2 leftBoth children receive the same prop. The mirrored one stays blue forever, because `useState` ignores its argument after the first render. Pick other colours from the menu to confirm.
Preview
Console
Nothing logged yet.Expected console output
Mirrored: blue
Direct: redEdit both boxes, then click the button. The first editor keeps your typing; the second gets a new `key`, so React creates a fresh editor that starts from the new draft. Topic 6.4 explains keys and resets in full.
Preview
Console
Nothing logged yet.// ❌ Works on screen, but renders twice with a stale value in between
// and still keeps two sources of truth.
function Message({ color }) {
const [c, setC] = useState(color);
useEffect(() => {
setC(color);
}, [color]);
return <p style={{ color: c }}>Hello</p>;
}
// ✅ Read the prop. Nothing to sync.
function Message({ color }) {
return <p style={{ color }}>Hello</p>;
}
// ✅ Expensive derived value? Cache it, don't store it (Topic 16.4).
const visible = useMemo(() => filterTodos(todos, filter), [todos, filter]);Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A stored total goes stale
In the to-do example, add const [total, setTotal] = useState(todos.length); and show <p>{total} items</p>. Then add a button that does setTodos([...todos, { id: 4, text: "New", done: false }]) and click it.
Break #2
The frozen prop
In the side-by-side example, choose green in the menu.
Myth vs fact
Myth
Calculating values on every render is wasteful, so store them.
Fact
Most calculations (filtering a few hundred items, summing a cart) take microseconds. Storing them costs bugs and an extra render. Cache with useMemo only when measuring shows it is slow.
Myth
useState(prop) keeps the state in sync with the prop.
Fact
The argument is used once, on the first render. After that React returns the stored state and ignores the argument.
Myth
Copying props into state is always wrong.
Fact
It is right when the prop is only a starting value (initialCount, defaultValue). Then ignoring later changes is the point.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Because
useState's argument is evaluated on every render even though it's used only once, pass a function for expensive initial values:useState(() => parse(bigText)). That's lazy initialisation; React calls the function only on the first render (and twice in StrictMode). - ▸
React's docs also describe storing the previous prop in state and calling
setStateduring render to adjust state when a prop changes. React allows setting the same component's state during render and immediately re-runs it before committing. It is a rare escape hatch; akeyor a pure calculation is almost always clearer (Topic 7.6). - ▸
Derived data and referential stability:
todos.filter(...)returns a new array each render. That is fine for rendering, but if you pass it to amemochild or an effect dependency, the new reference counts as a change. That's the real reasonuseMemoexists (Topic 16.4), not speed alone. - ▸
Uncontrolled inputs use the same idea:
defaultValueis a starting value the DOM owns afterwards, just asinitialXprops are starting values the component owns (Topic 5.3).
Remember this
- 1
A derived value is any value you can work out from props, state or other derived values: a total, a filtered list, a count, a full name, "is the form valid". In React you derive values with plain JavaScript in the component body, during render:
const total = items.reduce((sum, i) => sum + i.price, 0). Every render starts from the current state, so derived values are always up to date, with no extra code. - 2
Storing a derived value in state creates two sources of truth: the items and the stored total. Every place that changes the items must also remember to update the total. Miss one, and the screen shows a total that doesn't match the list. It also costs an extra render: you set the items, React renders, then something sets the total, and React renders again.
- 3
The most famous version of this bug is mirroring props in state:
function Message({ color }) { const [c, setC] = useState(color); ... }.useState(initial)uses its argument only on the first render of that component. When the parent later passes a newcolor, the state keeps the old one. The child looks frozen. The fix is to use the prop directly (color), or a value calculated from it. - 4
Beginners often try to repair the frozen prop with an effect that copies it into state whenever it changes. That works on screen but renders twice with stale data in between, and it keeps two sources of truth. Phase 7 explains effects, and Topic 7.6 ("You Might Not Need an Effect") shows why this is one of the most common effect mistakes. In this phase you won't need effects at all.
- 5
Copying a prop into state is correct when the prop is only a starting value and the component is meant to ignore later changes: a text box that starts with a saved draft, a counter that starts at
initialCount. Name such propsinitialXordefaultXso readers know later changes are ignored on purpose. If the parent ever needs to start over with a new value, give the child a differentkey(Topic 6.4). - 6
What about expensive calculations, such as filtering 10,000 rows? Still calculate during render. If measuring shows it's slow, wrap it in
useMemo(Topic 16.4), which caches the result until its inputs change. That is still derived state, only cached; it is never a second source of truth. With the React Compiler (Topic 16.5) this caching often happens automatically.
Explain it without notes
What is derived state and where should you compute it?
Explain the "mirror props in state" bug. Why does it happen?
When is it correct to initialise state from a prop?
A filtered list of 20,000 rows is slow to compute on every keystroke. Should you store it in state? What should you do instead?
Practice
A cart component stores items and total in state and updates both in addItem. Rewrite it with only the state it needs.
This component shows a stale price when the parent changes price: function Price({ price }) { const [p] = useState(price); return <b>₹{p}</b>; }. Fix it.
Trade-offs
- ↔
Calculating during render costs a little CPU each render; storing costs correctness and an extra render. Prefer calculating, and cache with
useMemoonly when a measurement shows a real cost. - ↔
Starting-value props (
initialX) give a component its own editable copy but mean the parent can't update it later except by resetting it with akey. If the parent must control the value all the time, make the component controlled instead (Topic 6.3).
Done when you can
I can tell facts (state) from derived values.
I calculate totals, counts and filtered lists in the component body.
I can explain why
useState(prop)freezes the prop's first value.I name starting-value props
initialXordefaultXand reset them with a key.I know
useMemocaches a derived value and doesn't replace calculating it.