Resetting State with Key
Reset component state by changing key: when remount is cleaner than effects that sync props into useState.
- react
- resetting-state
You open user A’s profile form, type half a bio, switch to user B — and still see A’s draft. Local state survived because React reused the same component instance at the same tree position. Changing key tells React this is a different instance: unmount the old one, mount a new one, run initial state again.
Docs: Preserving and Resetting State.
The pattern
function Page({ userId }) {
return (
<section>
<h1>Edit user</h1>
<UserForm key={userId} userId={userId} />
</section>
);
}
function UserForm({ userId }) {
const [bio, setBio] = useState('');
// fresh bio whenever userId-driven remount happens
return (
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
aria-label="Bio"
/>
);
}
Parent owns which entity is selected. Child owns draft UI state. Key bridges them without useEffect(() => setBio(''), [userId]).
Why not an effect?
// Works until it races with typing, network revalidation, or partial updates
useEffect(() => {
setBio(serverBio);
}, [userId, serverBio]);
Effects that copy props into state are a top footgun (derived state). Remounting is explicit: old draft is gone, subscriptions restart, refs reset.
When remount is wrong
| Goal | Prefer |
|---|---|
| Keep draft while parent re-renders | Stable key; do not key on unrelated props |
| Soft reset one field | setField(initial) in an event handler |
| Animate between states | Avoid full remount; keep instance |
| Expensive mount (charts, editors) | Explicit reset API via ref / imperative handle |
// Only reset when the document identity changes — not when theme toggles
<Editor key={doc.id} doc={doc} theme={theme} />
Putting theme in the key would destroy editor undo stacks on every palette change.
Lists and forms together
Multi-step wizards often key each step’s panel so leaving a step drops ephemeral UI, while lifted form state in the parent survives:
function Wizard({ step, data, onChange }) {
return (
<>
<StepPanel key={step} step={step} data={data} onChange={onChange} />
<nav>{/* next / back */}</nav>
</>
);
}
Lift durable answers; key the step chrome. See multi-step forms.
Combined with error recovery
Error boundaries often remount children by bumping a resetKey after “Try again,” clearing the crashed fiber’s poisoned state.
const [resetKey, setResetKey] = useState(0);
return (
<ErrorBoundary onReset={() => setResetKey((k) => k + 1)} key={resetKey}>
<Chart />
</ErrorBoundary>
);
Interview out-loud
“I reset state by changing the component’s key when the entity identity changes, which remounts the subtree with fresh useState. That is cleaner than effects that mirror props into state. I keep keys tied to identity, not to every prop that happens to change.”
Related on this site
Further reading
Production checklist
Before you ship a change in this area, walk the list out loud:
- What is the source of truth for the data on screen?
- What happens on remount, route change, and Strict Mode double-invoke?
- Which updates are urgent (input) versus deferrable (filter large lists)?
- Did you profile before adding memo, context splits, or virtualization?
- Is there an accessibility path: keyboard, focus, names, and errors?
If you cannot answer those, the API knowledge will not save the interview or the incident.
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.