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.

June 7, 20264 min read1 / 2

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.

TSX
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

TSX
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:

  • fullName derived from firstName and lastName
  • filteredItems derived from items and searchQuery
  • isFormValid derived 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 useState that is only ever updated inside a single useEffect
  • A useEffect whose entire body is a single setState call
  • 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.

Render 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 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

  1. A useState + useEffect that sets that state is almost always derived state in disguise. Remove both. Compute inline. The extra state is not careful -- it is extra complexity.
  2. 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.
  3. Try the plain calculation first. useMemo is for measured performance problems, not preemptive defense against ones that have not happened yet.

Further Reading and Watching