React Re-render vs Remount: What Actually Triggers Each
A practical guide to React re-render vs remount: what type, position, and key decide, why state resets unexpectedly, and how to force a remount.

You switch from one conversation to another in a chat app. Same <Chat /> component, new conversationId prop. The message list updates correctly — but the half-typed reply still sitting in the input box doesn't clear. It's still showing what you were typing to the previous person.
Nothing crashed. Nothing re-rendered incorrectly. React did exactly what you told it to do — you just told it to keep going, not to start over.
This is episode one of React Deep Dive, a weekly series on what React itself decides, not on JavaScript with a React import at the top. This first episode is about the distinction sitting underneath that chat bug and dozens like it: a re-render and a remount look almost identical on screen, but they are two completely different operations, and React chooses between them using rules that have nothing to do with the props you pass.
This article is written against React 19.2 (verified against the current npm and GitHub release, 19.2.8, published July 2026) and assumes React 19-era function components and hooks throughout.
By the end of this article you'll be able to:
- Tell, from the outside, whether a state reset you're seeing was a re-render or a remount
- Explain the actual rule React uses to decide — type and position in the tree, with
keyas an override - Predict when a conditional (
{cond && <A/>}, a ternary, a list without stable keys) causes a hidden remount - Force a remount on purpose with
key, instead of syncing state in an effect - Tell React's deliberate StrictMode double-invoke and the React Compiler apart from a real remount
You've written function components with useState and useEffect, and you've been surprised at least once by state that didn't reset when you expected it to (or did reset when you didn't). No class-component knowledge required.
- The problem: a draft that won't go away
- The mental model: state lives on a slot in the tree, not on your component
- Stage 1: same type, same slot — a re-render
- Stage 2: different type, same slot — a remount
- Stage 3: same type, different
key— a remount on purpose - Stage 4: lists, and why an unstable
keycauses accidental remounts - Edge cases and gotchas
- Best practices: choosing on purpose
- FAQ
- Cheat sheet
Here's the chat component, written the way most people write it first:
Click a different contact. MessageList correctly shows the new conversation's messages — its conversationId prop changed, and it renders from that prop every time. But draft is a piece of useState that belongs to Chat, and Chat never went away. It's the same instance it was a second ago, just handed new props. So draft is still "hey are we still on for—" from the conversation you just left.
The instinct is to "fix" this by syncing state in an effect:
This does clear the draft — one render late, visibly, and you're now fighting React's render cycle instead of using it. There's a better fix, and understanding why it works is the actual point of this article.
Here's the sentence that resolves the chat bug and most others like it:
The mental model: state doesn't belong to your component function. It belongs to the position that component occupies in the render tree for a given (type, key) pair. As long as the same type sits in the same slot on every render, React treats it as the same instance and keeps its state. Change the type at that slot, or change its
key, and React throws the old instance away — state included — and builds a new one from scratch.
"Re-render" and "remount" are the two outcomes of that decision:
- Re-render: same instance, new props flow in, existing hooks keep their state, effects re-run only if their dependencies changed.
- Remount: old instance is torn down (every effect's cleanup runs, all its state is discarded), a brand-new instance is created (state initializers run again, effects run as if for the first time).
Chat never remounted. Its slot — inside App, right after ContactList — held a <Chat> element on every render, so React kept reusing the same instance and just fed it new props. draft survived because there was never a moment where its instance ceased to exist.
Key concept: if you want to know whether something is a re-render or a remount, don't look at the props. Look at whether the slot still holds the same type-and-key pair it held on the previous render.
This is the default, and it's why props updating in place feels unremarkable:
Whatever causes App to render again — a prop change, a parent's setState — Counter renders again too, but it's still the same Counter instance. count is untouched by that; it only changes when you call setCount. This is the behavior every hook is built to assume: state persists across re-renders of the same instance, by design.
Now put two different component types in the same slot, switched by a condition:
Every time isLoggedIn flips, the slot inside AuthGate holds a different type — Dashboard one render, LoginForm the next. React doesn't try to reconcile a <button> inside LoginForm against a <button> inside Dashboard just because they're both buttons; when the type at a slot changes, React assumes the whole subtree changed and rebuilds it. LoginForm unmounts completely (any state, like a half-typed password, is gone), and Dashboard mounts as a fresh instance.
This is correct here — you want a stale login form gone once someone's authenticated. The trap is not noticing it happens just as readily somewhere you didn't intend it to, which Stage 4 covers.
Key concept: a remount isn't limited to "the component disappeared entirely." Toggling between two different types at the same JSX position is a remount of both — the old type unmounts, the new type mounts — even though something is on screen the whole time.
Back to the chat bug. Chat never remounts because it's always Chat in that slot — React has no reason to distrust the instance just because conversationId changed. But key overrides the type-and-position rule: two elements of the same type at the same slot are still treated as different instances if their keys differ. That's the tool for the chat bug:
Now switching contacts changes the key, so React sees "same type, different identity" at that slot and remounts Chat — the old instance (draft and all) is discarded, a fresh one is created with draft back at its initial ''. No effect syncing, no extra render, no fighting the render cycle: you told React the truth, which is that a different conversation is a different Chat.
This is a documented React pattern, not a hack — the React team's own guidance for "reset a component's state when a specific prop changes" is exactly this: give it a key derived from that prop.
key isn't only for forcing a remount deliberately — it's also how React tells list items apart, and getting it wrong causes unintentional remounts:
With an index key, "slot 0" is a fixed idea — whichever todo is first gets treated as the same instance as whatever was first before, even if it's a different todo now. Reorder or delete an item, and every TodoRow after that point gets handed a different todo's data while React thinks it's the same instance — any local state a row holds (an "editing" flag, an uncommitted edit) now belongs to the wrong item. With a stable id key, moving todo.id: 7 moves its instance and state right along with it, and only genuinely new or removed ids mount or unmount.
Key concept: key is not a rendering optimization detail you can skip. It's the identity React uses to decide re-render vs. remount for every item in a list, and an unstable one produces exactly the "state stuck to the wrong item" bugs Stage 1–3 just explained.
- A conditional render is a mount/unmount transition, not a hidden re-render.
{isOpen && <Modal />}mountsModalfresh every timeisOpenflips true — there's no instance to preserve across thefalsein between. - Position matters even without a type change. Moving the same JSX to a different parent, or a different sibling index without a key, can change its effective slot and trigger a remount — the rule is about tree position, not about writing a literally different tag.
- A remount reruns every effect from scratch, including expensive setup (subscriptions, timers, widget initialization). An unexpected remount is often the real cause of a "why did this widget reinitialize" bug.
- StrictMode's double-invoke is a deliberate dev-only mount → unmount → mount, meant to catch effects unsafe to run twice — not evidence of the remount rule misfiring, and it never runs in production.
- The React Compiler only changes whether a re-render is skipped, via automatic memoization. It doesn't touch the type/position/key identity rule — a
keychange still forces a remount the same way with or without it.
- Reach for
keywhen a prop change should mean "this is now a different thing," not just "new data." A conversation id, a selected record's id, a form you want fully reset between edits — these arekeycandidates. - Don't sync state to a prop in an effect just to reset it. A changing
keygets you there in one render via the initializer, instead of a state update chasing the prop one render behind. - Give every list item a stable, unique
keydrawn from the data, never the array index, unless the list is provably static and never reorders or filters. - Don't use a remount as a substitute for correct cleanup. A remount forces cleanup and a fresh mount as a side effect of identity, not as a design tool — write correct cleanup in the effect itself.
- Debugging a state-reset surprise? Ask which slot the component sits in, and whether its type or key changed there — faster than reading through the component's own logic.
Runs right in your browser — poke at it and watch the concept react live.
No, not by itself. A prop change flows into the same instance as a re-render, as long as the component's type and key at that tree position stay the same. Only a type change, a key change, or the element leaving and re-entering the tree causes a remount.
It changes the identity React uses for that slot. Two elements of the same type with different keys are treated as two different instances — React unmounts whichever one was there and mounts the new one fresh, discarding all of its state.
The most common causes are: a key that's computed from something that changes more often than you think, the component sitting inside a conditional branch that occasionally makes it disappear and reappear, or a list item receiving a different key than it had on the previous render because of an unstable index-based key.
It's a deliberate development-only mount → unmount → mount cycle designed to surface effects that behave incorrectly on a second mount. It exercises the same mechanism a real remount does, but it isn't triggered by the type/position/key rule — it happens regardless, purely to test your cleanup.
No. The compiler's job is deciding whether a re-render can be skipped through automatic memoization; it doesn't touch the reconciliation rule that decides re-render versus remount. A key change forces a remount the same way whether or not the compiler is enabled.
Think it clicked? Take the 8-question quiz →
Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.
| Situation | Re-render or remount? | Why |
|---|---|---|
| Parent passes new props, same type & key, same slot | Re-render | React reuses the instance; only props change |
key changes on the same type at the same slot | Remount | key overrides type/position identity |
Type changes at the same slot (isX ? <A/> : <B/>) | Remount | Different type ⇒ React assumes a different subtree |
{cond && <Modal/>} toggling cond | Mount ↔ unmount | The element only exists in the tree while cond is true |
List item reordered with a stable id key | Re-render (instance moves with it) | Identity travels with the key, not the position |
| List item reordered with an index key | Effectively a remount/mixup down the list | The index, not the item, is the identity |
setState called inside the component | Re-render | Same instance, new state, same identity |
| Ancestor unmounts | All descendants unmount | No slot survives without its parent |
- State belongs to a (type, key) pair at a tree position, not to your component function — that's the one rule behind every re-render/remount surprise.
- Re-render: same instance, new props, existing state and effect subscriptions survive.
- Remount: old instance torn down (cleanup runs, state discarded), new instance built from scratch.
keyis your override — change it to force a remount on purpose instead of syncing state in an effect.- List keys are identity, not decoration. An unstable key produces exactly the "wrong item's state" bugs that a good key prevents.
- StrictMode's double-invoke and the React Compiler's memoization both sit alongside this rule — neither one changes it.
The draft didn't clear because Chat never stopped being the same Chat — React had no reason to think otherwise, because nothing about its type or its position (or, before the fix, its key) ever changed. Adding key={conversationId} didn't patch around the bug; it told React the one thing it needed to know: a different conversation is a different Chat, and it should be built fresh.
The next time state resets when you didn't expect it — or refuses to when you did — ask the tree-slot question first: did the type or the key at that position actually change? You'll usually have the answer before you've read a single line of the component's own code.
What's the state-reset bug that cost you the most time to track down — and did it turn out to be a key?
🚀 Want more like this? Every guide, playground, and quiz lives on bestpractic.org — open it and sign up free so the next one finds you.
Thanks for reading! Let's stay connected:
- ⭐ GitHub — follow me and star the projects: github.com/parsajiravand
- 💬 Discord — join the frontend best-practices community: discord.gg/d9KRhuAwQ
- 📸 Instagram — frontend best practices, daily: @bestpractice___
Keep reading
One post a day, in your inbox
Each one with a runnable playground and a quiz. No pitch, no digest, unsubscribe in one click.
0 comments
Sign in to join the discussion, like comments, and save articles for later.