Derived State
Not every value your component shows needs to live in useState. If a value can be calculated from props or other state, you should compute it during render rather than store a separate copy and try to keep that copy in sync. Storing values that can be derived is one of the most common sources of React bugs—the copy drifts out of date, you forget to update it in one of three handlers, and the UI shows stale numbers. The fix is almost always to delete the state and calculate the value inline.
What “derived state” means
Derived state is any value you can produce from the data you already have. If you have a list of cart items in state, the total price, the item count, and the “is empty” flag are all derived—they are functions of the cart, not independent facts. The render function already runs on every change, so a plain const recomputes automatically and is always correct.
import { useState } from "react";
function Cart() {
const [items, setItems] = useState([
{ id: 1, name: "Keyboard", price: 80, qty: 1 },
{ id: 2, name: "Mouse", price: 25, qty: 2 },
]);
// Derived — recalculated every render, never stored:
const itemCount = items.reduce((sum, i) => sum + i.qty, 0);
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
const isEmpty = items.length === 0;
return (
<div>
<p>{isEmpty ? "Cart is empty" : `${itemCount} items — $${total}`}</p>
</div>
);
}
itemCount, total, and isEmpty have no setters and no useEffect. They simply reflect items at the moment of render. Change items once and all three update for free.
Why redundant state causes bugs
The anti-pattern is mirroring a derived value into its own state and syncing it manually. Consider a search box that keeps the filtered results in state.
// ❌ Anti-pattern: redundant state synced by hand
function SearchBox({ products }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(products); // redundant!
function handleChange(e) {
const next = e.target.value;
setQuery(next);
// You must remember to refilter here, AND anywhere products changes...
setFiltered(products.filter((p) => p.name.includes(next)));
}
return (
<>
<input value={query} onChange={handleChange} />
<ul>{filtered.map((p) => <li key={p.id}>{p.name}</li>)}</ul>
</>
);
}
This has two bugs waiting to happen. If the products prop changes, filtered keeps showing the old list because nothing refilters it. And if you add another way to change the query, you have to duplicate the filtering call there too. The two pieces of state can disagree.
The corrected version stores only the minimal source of truth—the query—and derives the rest.
// ✅ Derive during render: one source of truth
function SearchBox({ products }) {
const [query, setQuery] = useState("");
const filtered = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase())
);
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>{filtered.map((p) => <li key={p.id}>{p.name}</li>)}</ul>
</>
);
}
Now filtered cannot go stale. It is recomputed from the current products and the current query every render, so it is correct whether the change came from typing or from a new prop.
Rule of thumb: if you ever write a
useEffectwhose only job is to call a setter that copies one piece of state into another, you almost certainly want derived state instead.
When memoization helps
Recomputing on every render is usually fine—filtering a few hundred items costs nothing. Reach for useMemo only when the calculation is genuinely expensive and its inputs change less often than the component re-renders. useMemo caches the result and recomputes only when a dependency changes.
import { useMemo, useState } from "react";
function ReportTable({ rows }) {
const [sortKey, setSortKey] = useState("name");
// Expensive sort: only recompute when rows or sortKey change
const sorted = useMemo(() => {
return [...rows].sort((a, b) => String(a[sortKey]).localeCompare(b[sortKey]));
}, [rows, sortKey]);
return (
<table>
<tbody>
{sorted.map((r) => <tr key={r.id}><td>{r.name}</td></tr>)}
</tbody>
</table>
);
}
If ReportTable re-renders because of an unrelated state change, the sort is skipped and the cached sorted array is reused. Note that useMemo is an optimization, not a correctness tool—the code is correct without it; the memo just avoids wasted work.
| Situation | What to do |
|---|---|
| Value computable from props/state | Plain const during render |
| Cheap computation (map, filter, sum) | Plain const—don’t memoize |
| Expensive computation, stable inputs | useMemo(fn, [deps]) |
| Truly independent user input | Store in useState |
Best Practices
- Keep the minimal set of independent values in state; everything else, calculate during render.
- Prefer a plain
constoveruseStatewhenever a value is a pure function of existing data. - Delete any
useEffectthat exists only to copy state into more state—derive it instead. - Don’t reach for
useMemoby default; profile first, then memoize only expensive calculations with stable inputs. - Give
useMemoa complete, accurate dependency array so the cached value never goes stale. - Treat derived values as read-only outputs of render—never try to “set” them directly.