The Deriving State Anti-Pattern
Calculating derived values inside useEffect is one of the most common React mistakes. Here is why it causes extra renders, and the simpler fix that belongs right in the render function.
The first anti-pattern in this state management at scale series is derived state -- specifically, the habit of computing it inside a useEffect. I have written this in codebases I was paid to maintain, with full confidence that I was being careful.
type Task = { id: string; name: string; hours: number };
function TimeTracker() {
const [tasks, setTasks] = useState<Task[]>([]);
const [totalHours, setTotalHours] = useState(0);
useEffect(() => {
setTotalHours(tasks.reduce((sum, t) => sum + t.hours, 0));
}, [tasks]);
// ...
}The dependency array is correct. The calculation is right. The data flows in the right direction.
There is still a problem.
What Goes Wrong
When tasks changes, React renders the component once. The first render shows the old totalHours -- the value from before the change. Then the effect fires, calls setTotalHours, and React renders again with the correct value.
Every task update triggers two renders. The stale window between them is real.
The deeper problem is not the extra render. It is that there are now two pieces of state that must stay synchronized. totalHours has no independent existence -- it is entirely derived from tasks. You have introduced a relationship between two pieces of state and made yourself responsible for enforcing it on every update.
That enforcement code is the useEffect.
Enforcement code is where bugs live.
The Fix
function TimeTracker() {
const [tasks, setTasks] = useState<Task[]>([]);
const totalHours = tasks.reduce((sum, t) => sum + t.hours, 0);
// ...
}totalHours is now a constant calculated during render. Every time tasks changes, the component re-renders and totalHours is recalculated in the same pass, from the same data, with no stale window and no extra state update.
No useState. No useEffect. No synchronization to maintain.
The rule: if a value can be fully derived from existing state or props, calculate it in render.
Other values that follow the same rule:
fullNamederived fromfirstNameandlastNamefilteredItemsderived fromitemsandsearchQueryisFormValidderived from each form field's value
The source of truth is the data you actually own. Everything else is a calculation.
The useMemo Question
Once you start computing values directly in render, someone will ask whether this should be wrapped in useMemo.
Usually not.
useMemo is the right tool when the calculation is genuinely expensive -- parsing a large dataset, sorting thousands of records, running a heavy algorithm. For a reduce over a handful of tasks, useMemo adds call overhead without saving any real work.
Start with the plain calculation. Profile if something feels slow. Reach for useMemo only when you have evidence of a problem.
Wrapping every derived value in useMemo "just in case" is premature optimization. It makes the code harder to read and provides no measurable benefit in the common case.
How to Spot It
The smell always has the same shape:
- A
useStatethat is only ever updated inside a singleuseEffect - A
useEffectwhose entire body is a singlesetStatecall - State whose value is fully computable from other state you already have
In larger codebases this hides behind more logic -- a transformation before setting state, multiple values in the dependency array. The shape is the same regardless.
Once you see it, it is hard to unsee.
ExpandRender cycle comparison: anti-pattern shows tasks change triggering render #1 then useEffect firing then render #2; fix shows tasks change triggering render #1 with total computed inline in the same render
There is a related anti-pattern where the problem is not about computation but about values that should not be in state at all -- values that exist for internal bookkeeping and have no business triggering a re-render. Redundant State and When to Use Refs covers both.
The Essentials
- A
useState+useEffectthat sets that state is almost always derived state in disguise. Remove both. Compute inline. The extra state is not careful -- it is extra complexity. - Every extra piece of state is a synchronization contract. When two pieces of state represent the same fact, you are now responsible for keeping them in sync. That contract will eventually break.
- Try the plain calculation first.
useMemois for measured performance problems, not preemptive defense against ones that have not happened yet.
Further Reading and Watching
- You Might Not Need an Effect -- React Docs: The definitive reference on when
useEffectis the wrong tool, with specific sections on derived state, event-based logic, and data fetching. - Goodbye, useEffect -- David Khourshid, ViteConf 2022: A deep dive into the most common misuses of
useEffectand what to reach for instead. Note: verify this YouTube link before publishing.
Keep reading