ESC

Type to search the knowledge base.

Derived State Anti-Patterns

Stop copying props into state: derive during render, reset with keys, and the rare cases where mirrored state is intentional.

intermediate4 min read
  • react
  • derived-state

The bug shows up as “I updated the parent prop but the child still shows the old value.” Almost always someone did this:

// Anti-pattern: props copied into state once
function UserBadge({ user }) {
  const [name, setName] = useState(user.name);
  return <input value={name} onChange={(e) => setName(e.target.value)} />;
}

useState(user.name) uses user.name only on first mount. Later prop changes are ignored. That is derived state done wrong.

Docs: You Might Not Need an Effect, Choosing the State Structure.

Prefer derive during render

If you can compute it from props or existing state, do not store it:

function CartSummary({ items }) {
  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
  const isEmpty = items.length === 0;
  return (
    <p>{isEmpty ? 'Empty cart' : `Total: $${total.toFixed(2)}`}</p>
  );
}

No useEffect that setTotal when items change. No second source of truth to desync.

For expensive pure calcs, useMemo — still not a second state:

const filtered = useMemo(
  () => items.filter((i) => i.name.includes(query)),
  [items, query]
);

Reset local state when identity changes

If you need local state that must reset when an entity changes (selected customer, document id), prefer key on the child:

<Editor key={documentId} documentId={documentId} initialText={doc.body} />

See Resetting State with key. Remount gives fresh useState initial values without effect glue.

The effect-based mirror is usually worse:

// Common but easy to get wrong
useEffect(() => {
  setName(user.name);
}, [user.name]);

It fights user keystrokes if user refreshes mid-edit, causes extra renders, and hides ownership.

Controlled components are not this bug

function SearchInput({ value, onChange }) {
  return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}

State lives in the parent on purpose. The child is controlled — not a failed derivation.

Rare legitimate mirrored state

Case Approach
Draft edit buffer until Save Local state; commit via callback; reset with key or explicit cancel
Animation from previous value Store previous in a ref, not dual React state
Uncontrolled input initial only defaultValue — not value plus state copy
function DraftTitle({ savedTitle, onSave }) {
  const [draft, setDraft] = useState(savedTitle);
  return (
    <>
      <input value={draft} onChange={(e) => setDraft(e.target.value)} />
      <button type="button" onClick={() => onSave(draft)}>Save</button>
      <button type="button" onClick={() => setDraft(savedTitle)}>Reset</button>
    </>
  );
}

Document ownership: draft is local until save.

Footguns checklist

  1. useState(props.x) without a remount strategy.
  2. useEffect(() => setX(props.x), [props.x]) as default architecture.
  3. Storing filtered lists in state instead of computing them.
  4. Dual “loading” flags that a data library already models.

Interview out-loud

“I avoid storing values that can be computed from props or existing state. Copying props into useState only uses the initial value — a classic bug. I reset local state with key when entity identity changes, and I use controlled components when the parent owns the data.”

Further reading

Edge cases worth rehearsing

Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.

Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.

When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.

Related guides