Derived State Anti-Patterns
Stop copying props into state: derive during render, reset with keys, and the rare cases where mirrored state is intentional.
- 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
useState(props.x)without a remount strategy.useEffect(() => setX(props.x), [props.x])as default architecture.- Storing filtered lists in state instead of computing them.
- 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.”
Related on this site
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
- useCopyToClipboard HookCopy text with navigator.clipboard, fallback execCommand, transient copied state, and permissions that fail in insecure contexts.
- useDebouncedValue HookDebounce a changing value in React without firing effects on every keystroke. Cleanup, leading vs trailing, and the stale timer that updates unmounted state.
- useEventListener HookAttach a window, document, or element listener with a stable handler ref so you do not rebind on every render. Capture option and target changes.
- useFetch With AbortControllerFetch in an effect with a signal, ignore aborted errors, and prevent the slow response from winning a race after the url changes.
- useIdle HookDetect idle users: reset a timer on input events, fire idle after timeout, and decide whether mount starts the timer immediately.