[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-react-weekly-rerender-vs-remount":44,"search-suggestions":60,"quiz-article-react-weekly-rerender-vs-remount":106},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a04446-3790-705e-b277-63c77b05dc22","react-weekly-rerender-vs-remount","PRACTICE_QUIZ","React Re-render vs Remount","Test what actually decides whether React re-renders a component in place or tears it down and builds a fresh one — the type\u002Fposition\u002Fkey rule, list keys, StrictMode, and the React Compiler.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,84,88,92,95,99,103],{"slug":62,"name":63,"articles":64},"webdev","Webdev",72,{"slug":66,"name":67,"articles":68},"frontend","Frontend",62,{"slug":70,"name":71,"articles":72},"javascript","Javascript",60,{"slug":74,"name":75,"articles":76},"css","Css",28,{"slug":78,"name":79,"articles":80},"typescript","Typescript",11,{"slug":82,"name":83,"articles":80},"tutorial","Tutorial",{"slug":85,"name":86,"articles":87},"performance","Performance",9,{"slug":89,"name":90,"articles":91},"react","React",6,{"slug":93,"name":94,"articles":91},"grammar","Grammar",{"slug":96,"name":97,"articles":98},"node","Node",5,{"slug":100,"name":101,"articles":102},"programming","Programming",4,{"slug":104,"name":105,"articles":102},"html","Html",{"id":107,"slug":46,"title":108,"subtitle":52,"excerpt":109,"coverUrl":110,"locale":13,"readingMinutes":111,"publishedAt":112,"viewCount":113,"likeCount":19,"commentCount":19,"author":114,"vertical":119,"topic":120,"tags":121,"_count":126,"playground":128,"body":130,"bodyMd":472,"seo":473,"translationGroupId":475,"series":52,"podcastUrl":52,"verticalId":5,"thread":476,"assessments":478,"translations":481,"quiz":483},"01a04446-373a-775b-8aaf-b72c8952123d","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.","\u002Fmedia\u002Fcovers\u002Freact-weekly-rerender-vs-remount.png",13,"2026-08-29T12:32:41.200Z",47,{"id":115,"name":116,"username":117,"avatarUrl":52,"headline":118},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":89,"name":90},[122,123,124,125],{"slug":89,"name":90,"color":52},{"slug":70,"name":71,"color":52},{"slug":82,"name":83,"color":52},{"slug":62,"name":63,"color":52},{"assessments":127},1,{"slug":46,"title":129},"React re-render vs remount — interactive playground",{"blocks":131,"version":127},[132,136,139,142,145,150,153,162,165,168,171,184,188,191,197,200,203,207,210,213,216,220,223,228,231,234,237,240,244,247,250,253,257,260,263,266,270,273,277,280,283,287,290,294,297,300,303,311,314,322,325,328,331,334,337,340,343,346,350,353,357,360,364,367,370,373,376,379,382,385,388,391,429,433,436,445,448,451,454,457,460,463,466],{"id":133,"html":134,"type":135},"b1","\u003Cp>You switch from one conversation to another in a chat app. Same \u003Ccode>&lt;Chat \u002F&gt;\u003C\u002Fcode> component, new \u003Ccode>conversationId\u003C\u002Fcode> prop. The message list updates correctly — but the half-typed reply still sitting in the input box doesn&#39;t clear. It&#39;s still showing what you were typing to the \u003Cem>previous\u003C\u002Fem> person.\u003C\u002Fp>","paragraph",{"id":137,"html":138,"type":135},"b2","\u003Cp>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.\u003C\u002Fp>",{"id":140,"html":141,"type":135},"b3","\u003Cp>This is episode one of \u003Cstrong>React Deep Dive\u003C\u002Fstrong>, 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 \u003Cstrong>re-render\u003C\u002Fstrong> and a \u003Cstrong>remount\u003C\u002Fstrong> 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.\u003C\u002Fp>",{"id":143,"html":144,"type":135},"b4","\u003Cp>This article is written against \u003Cstrong>React 19.2\u003C\u002Fstrong> (verified against the current npm and GitHub release, 19.2.8, published July 2026) and assumes React 19-era function components and hooks throughout.\u003C\u002Fp>",{"id":146,"html":147,"text":148,"type":149,"level":31},"b5","What you&#39;ll learn","What you'll learn","heading",{"id":151,"html":152,"type":135},"b6","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":154,"type":155,"items":156,"ordered":18},"b7","list",[157,158,159,160,161],"Tell, from the outside, whether a state reset you&#39;re seeing was a re-render or a remount","Explain the actual rule React uses to decide — type and position in the tree, with \u003Ccode>key\u003C\u002Fcode> as an override","Predict when a conditional (\u003Ccode>{cond &amp;&amp; &lt;A\u002F&gt;}\u003C\u002Fcode>, a ternary, a list without stable keys) causes a hidden remount","Force a remount \u003Cstrong>on purpose\u003C\u002Fstrong> with \u003Ccode>key\u003C\u002Fcode>, instead of syncing state in an effect","Tell React&#39;s deliberate StrictMode double-invoke and the React Compiler apart from a real remount",{"id":163,"html":164,"text":164,"type":149,"level":31},"b8","Who this is for",{"id":166,"html":167,"type":135},"b9","\u003Cp>You&#39;ve written function components with \u003Ccode>useState\u003C\u002Fcode> and \u003Ccode>useEffect\u003C\u002Fcode>, and you&#39;ve been surprised at least once by state that didn&#39;t reset when you expected it to (or did reset when you didn&#39;t). No class-component knowledge required.\u003C\u002Fp>",{"id":169,"html":170,"text":170,"type":149,"level":31},"b10","Table of contents",{"id":172,"type":155,"items":173,"ordered":18},"b11",[174,175,176,177,178,179,180,181,182,183],"\u003Ca href=\"#the-problem-a-draft-that-wont-go-away\">The problem: a draft that won&#39;t go away\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-state-lives-on-a-slot-in-the-tree-not-on-your-component\">The mental model: state lives on a slot in the tree, not on your component\u003C\u002Fa>","\u003Ca href=\"#stage-1-same-type-same-slot--a-re-render\">Stage 1: same type, same slot — a re-render\u003C\u002Fa>","\u003Ca href=\"#stage-2-different-type-same-slot--a-remount\">Stage 2: different type, same slot — a remount\u003C\u002Fa>","\u003Ca href=\"#stage-3-same-type-different-key--a-remount-on-purpose\">Stage 3: same type, different \u003Ccode>key\u003C\u002Fcode> — a remount on purpose\u003C\u002Fa>","\u003Ca href=\"#stage-4-lists-and-why-an-unstable-key-causes-accidental-remounts\">Stage 4: lists, and why an unstable \u003Ccode>key\u003C\u002Fcode> causes accidental remounts\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices-choosing-on-purpose\">Best practices: choosing on purpose\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>",{"id":185,"html":186,"text":187,"type":149,"level":31},"b12","The problem: a draft that won&#39;t go away","The problem: a draft that won't go away",{"id":189,"html":190,"type":135},"b13","\u003Cp>Here&#39;s the chat component, written the way most people write it first:\u003C\u002Fp>",{"id":192,"code":193,"type":194,"language":195,"highlight":196},"b14","function Chat({ conversationId, contactName }) {\n  const [draft, setDraft] = useState('');\n\n  useEffect(() => {\n    \u002F\u002F subscribe to messages for conversationId\n  }, [conversationId]);\n\n  return (\n    \u003Cdiv>\n      \u003Ch3>Chat with {contactName}\u003C\u002Fh3>\n      \u003CMessageList conversationId={conversationId} \u002F>\n      \u003Ctextarea\n        value={draft}\n        onChange={(e) => setDraft(e.target.value)}\n        placeholder=\"Type a message…\"\n      \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction App() {\n  const [activeId, setActiveId] = useState('alice');\n  const contact = CONTACTS[activeId];\n  return (\n    \u003C>\n      \u003CContactList onSelect={setActiveId} \u002F>\n      \u003CChat conversationId={activeId} contactName={contact.name} \u002F>\n    \u003C\u002F>\n  );\n}","code","jsx",[],{"id":198,"html":199,"type":135},"b15","\u003Cp>Click a different contact. \u003Ccode>MessageList\u003C\u002Fcode> correctly shows the new conversation&#39;s messages — its \u003Ccode>conversationId\u003C\u002Fcode> prop changed, and it renders from that prop every time. But \u003Ccode>draft\u003C\u002Fcode> is a piece of \u003Ccode>useState\u003C\u002Fcode> that belongs to \u003Ccode>Chat\u003C\u002Fcode>, and \u003Ccode>Chat\u003C\u002Fcode> never went away. It&#39;s the \u003Cem>same instance\u003C\u002Fem> it was a second ago, just handed new props. So \u003Ccode>draft\u003C\u002Fcode> is still \u003Ccode>&quot;hey are we still on for—&quot;\u003C\u002Fcode> from the conversation you just left.\u003C\u002Fp>",{"id":201,"html":202,"type":135},"b16","\u003Cp>The instinct is to &quot;fix&quot; this by syncing state in an effect:\u003C\u002Fp>",{"id":204,"code":205,"type":194,"language":195,"highlight":206},"b17","\u002F\u002F ❌ works, but for the wrong reason, and it costs an extra render\nuseEffect(() => {\n  setDraft('');\n}, [conversationId]);",[],{"id":208,"html":209,"type":135},"b18","\u003Cp>This does clear the draft — one render late, visibly, and you&#39;re now fighting React&#39;s render cycle instead of using it. There&#39;s a better fix, and understanding why it works is the actual point of this article.\u003C\u002Fp>",{"id":211,"html":212,"text":212,"type":149,"level":31},"b19","The mental model: state lives on a slot in the tree, not on your component",{"id":214,"html":215,"type":135},"b20","\u003Cp>Here&#39;s the sentence that resolves the chat bug and most others like it:\u003C\u002Fp>",{"id":217,"html":218,"type":219},"b21","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> state doesn&#39;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 \u003Cem>the same instance\u003C\u002Fem> and keeps its state. Change the type at that slot, or change its \u003Ccode>key\u003C\u002Fcode>, and React throws the old instance away — state included — and builds a new one from scratch.\u003C\u002Fp>\n","quote",{"id":221,"html":222,"type":135},"b22","\u003Cp>&quot;Re-render&quot; and &quot;remount&quot; are the two outcomes of that decision:\u003C\u002Fp>",{"id":224,"type":155,"items":225,"ordered":18},"b23",[226,227],"\u003Cstrong>Re-render\u003C\u002Fstrong>: same instance, new props flow in, existing hooks keep their state, effects re-run only if their dependencies changed.","\u003Cstrong>Remount\u003C\u002Fstrong>: old instance is torn down (every effect&#39;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).",{"id":229,"html":230,"type":135},"b24","\u003Cp>\u003Ccode>Chat\u003C\u002Fcode> never remounted. Its slot — inside \u003Ccode>App\u003C\u002Fcode>, right after \u003Ccode>ContactList\u003C\u002Fcode> — held a \u003Ccode>&lt;Chat&gt;\u003C\u002Fcode> element on every render, so React kept reusing the same instance and just fed it new props. \u003Ccode>draft\u003C\u002Fcode> survived because there was never a moment where its instance ceased to exist.\u003C\u002Fp>",{"id":232,"html":233,"type":135},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> if you want to know whether something is a re-render or a remount, don&#39;t look at the props. Look at whether the \u003Cem>slot\u003C\u002Fem> still holds the same type-and-key pair it held on the previous render.\u003C\u002Fp>",{"id":235,"html":236,"text":236,"type":149,"level":31},"b26","Stage 1: same type, same slot — a re-render",{"id":238,"html":239,"type":135},"b27","\u003Cp>This is the default, and it&#39;s why props updating in place feels unremarkable:\u003C\u002Fp>",{"id":241,"code":242,"type":194,"language":195,"highlight":243},"b28","function Counter({ label }) {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cbutton onClick={() => setCount((c) => c + 1)}>\n      {label}: {count}\n    \u003C\u002Fbutton>\n  );\n}\n\nfunction App({ label }) {\n  return \u003CCounter label={label} \u002F>; \u002F\u002F same slot, same type, every render\n}",[],{"id":245,"html":246,"type":135},"b29","\u003Cp>Whatever causes \u003Ccode>App\u003C\u002Fcode> to render again — a prop change, a parent&#39;s \u003Ccode>setState\u003C\u002Fcode> — \u003Ccode>Counter\u003C\u002Fcode> renders again too, but it&#39;s still the same \u003Ccode>Counter\u003C\u002Fcode> instance. \u003Ccode>count\u003C\u002Fcode> is untouched by that; it only changes when \u003Cem>you\u003C\u002Fem> call \u003Ccode>setCount\u003C\u002Fcode>. This is the behavior every hook is built to assume: state persists across re-renders of the same instance, by design.\u003C\u002Fp>",{"id":248,"html":249,"text":249,"type":149,"level":31},"b30","Stage 2: different type, same slot — a remount",{"id":251,"html":252,"type":135},"b31","\u003Cp>Now put two different component types in the same slot, switched by a condition:\u003C\u002Fp>",{"id":254,"code":255,"type":194,"language":195,"highlight":256},"b32","function AuthGate({ isLoggedIn }) {\n  return isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>;\n}",[],{"id":258,"html":259,"type":135},"b33","\u003Cp>Every time \u003Ccode>isLoggedIn\u003C\u002Fcode> flips, the slot inside \u003Ccode>AuthGate\u003C\u002Fcode> holds a different \u003Cem>type\u003C\u002Fem> — \u003Ccode>Dashboard\u003C\u002Fcode> one render, \u003Ccode>LoginForm\u003C\u002Fcode> the next. React doesn&#39;t try to reconcile a \u003Ccode>&lt;button&gt;\u003C\u002Fcode> inside \u003Ccode>LoginForm\u003C\u002Fcode> against a \u003Ccode>&lt;button&gt;\u003C\u002Fcode> inside \u003Ccode>Dashboard\u003C\u002Fcode> just because they&#39;re both buttons; when the type at a slot changes, React assumes the whole subtree changed and rebuilds it. \u003Ccode>LoginForm\u003C\u002Fcode> unmounts completely (any state, like a half-typed password, is gone), and \u003Ccode>Dashboard\u003C\u002Fcode> mounts as a fresh instance.\u003C\u002Fp>",{"id":261,"html":262,"type":135},"b34","\u003Cp>This is correct here — you \u003Cem>want\u003C\u002Fem> a stale login form gone once someone&#39;s authenticated. The trap is not noticing it happens just as readily somewhere you didn&#39;t intend it to, which Stage 4 covers.\u003C\u002Fp>",{"id":264,"html":265,"type":135},"b35","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> a remount isn&#39;t limited to &quot;the component disappeared entirely.&quot; Toggling between two different types at the \u003Cem>same JSX position\u003C\u002Fem> is a remount of both — the old type unmounts, the new type mounts — even though something is on screen the whole time.\u003C\u002Fp>",{"id":267,"html":268,"text":269,"type":149,"level":31},"b36","Stage 3: same type, different \u003Ccode>key\u003C\u002Fcode> — a remount on purpose","Stage 3: same type, different key — a remount on purpose",{"id":271,"html":272,"type":135},"b37","\u003Cp>Back to the chat bug. \u003Ccode>Chat\u003C\u002Fcode> never remounts because it&#39;s always \u003Ccode>Chat\u003C\u002Fcode> in that slot — React has no reason to distrust the instance just because \u003Ccode>conversationId\u003C\u002Fcode> changed. But \u003Ccode>key\u003C\u002Fcode> overrides the type-and-position rule: two elements of the same type at the same slot are still treated as different instances if their \u003Ccode>key\u003C\u002Fcode>s differ. That&#39;s the tool for the chat bug:\u003C\u002Fp>",{"id":274,"code":275,"type":194,"language":195,"highlight":276},"b38","\u002F\u002F ✅ key makes conversationId part of the instance's identity\n\u003CChat key={activeId} conversationId={activeId} contactName={contact.name} \u002F>",[],{"id":278,"html":279,"type":135},"b39","\u003Cp>Now switching contacts changes the \u003Ccode>key\u003C\u002Fcode>, so React sees &quot;same type, different identity&quot; at that slot and remounts \u003Ccode>Chat\u003C\u002Fcode> — the old instance (draft and all) is discarded, a fresh one is created with \u003Ccode>draft\u003C\u002Fcode> back at its initial \u003Ccode>&#39;&#39;\u003C\u002Fcode>. No effect syncing, no extra render, no fighting the render cycle: you told React the truth, which is that a different conversation \u003Cem>is\u003C\u002Fem> a different \u003Ccode>Chat\u003C\u002Fcode>.\u003C\u002Fp>",{"id":281,"html":282,"type":135},"b40","\u003Cp>This is a documented React pattern, not a hack — the React team&#39;s own guidance for &quot;reset a component&#39;s state when a specific prop changes&quot; is exactly this: give it a \u003Ccode>key\u003C\u002Fcode> derived from that prop.\u003C\u002Fp>",{"id":284,"html":285,"text":286,"type":149,"level":31},"b41","Stage 4: lists, and why an unstable \u003Ccode>key\u003C\u002Fcode> causes accidental remounts","Stage 4: lists, and why an unstable key causes accidental remounts",{"id":288,"html":289,"type":135},"b42","\u003Cp>\u003Ccode>key\u003C\u002Fcode> isn&#39;t only for forcing a remount deliberately — it&#39;s also how React tells list items apart, and getting it wrong causes \u003Cem>unintentional\u003C\u002Fem> remounts:\u003C\u002Fp>",{"id":291,"code":292,"type":194,"language":195,"highlight":293},"b43","\u002F\u002F ❌ index as key: correct-looking, quietly wrong once the list reorders\n{todos.map((todo, i) => (\n  \u003CTodoRow key={i} todo={todo} \u002F>\n))}\n\n\u002F\u002F ✅ a stable id that travels with the item\n{todos.map((todo) => (\n  \u003CTodoRow key={todo.id} todo={todo} \u002F>\n))}",[],{"id":295,"html":296,"type":135},"b44","\u003Cp>With an index key, &quot;slot 0&quot; is a fixed idea — whichever todo is first gets treated as the same instance as whatever was first before, even if it&#39;s a different todo now. Reorder or delete an item, and every \u003Ccode>TodoRow\u003C\u002Fcode> after that point gets handed a \u003Cem>different\u003C\u002Fem> todo&#39;s data while React thinks it&#39;s the same instance — any local state a row holds (an &quot;editing&quot; flag, an uncommitted edit) now belongs to the wrong item. With a stable id key, moving \u003Ccode>todo.id: 7\u003C\u002Fcode> moves its instance and state right along with it, and only genuinely new or removed ids mount or unmount.\u003C\u002Fp>",{"id":298,"html":299,"type":135},"b45","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>key\u003C\u002Fcode> is not a rendering optimization detail you can skip. It&#39;s the identity React uses to decide re-render vs. remount for every item in a list, and an unstable one produces exactly the &quot;state stuck to the wrong item&quot; bugs Stage 1–3 just explained.\u003C\u002Fp>",{"id":301,"html":302,"text":302,"type":149,"level":31},"b46","Edge cases and gotchas",{"id":304,"type":155,"items":305,"ordered":18},"b47",[306,307,308,309,310],"\u003Cstrong>A conditional render is a mount\u002Funmount transition, not a hidden re-render.\u003C\u002Fstrong> \u003Ccode>{isOpen &amp;&amp; &lt;Modal \u002F&gt;}\u003C\u002Fcode> mounts \u003Ccode>Modal\u003C\u002Fcode> fresh every time \u003Ccode>isOpen\u003C\u002Fcode> flips true — there&#39;s no instance to preserve across the \u003Ccode>false\u003C\u002Fcode> in between.","\u003Cstrong>Position matters even without a type change.\u003C\u002Fstrong> 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.","\u003Cstrong>A remount reruns every effect from scratch\u003C\u002Fstrong>, including expensive setup (subscriptions, timers, widget initialization). An unexpected remount is often the real cause of a &quot;why did this widget reinitialize&quot; bug.","\u003Cstrong>StrictMode&#39;s double-invoke is a deliberate dev-only mount → unmount → mount\u003C\u002Fstrong>, meant to catch effects unsafe to run twice — not evidence of the remount rule misfiring, and it never runs in production.","\u003Cstrong>The React Compiler only changes whether a re-render is skipped\u003C\u002Fstrong>, via automatic memoization. It doesn&#39;t touch the type\u002Fposition\u002Fkey identity rule — a \u003Ccode>key\u003C\u002Fcode> change still forces a remount the same way with or without it.",{"id":312,"html":313,"text":313,"type":149,"level":31},"b48","Best practices: choosing on purpose",{"id":315,"type":155,"items":316,"ordered":18},"b49",[317,318,319,320,321],"\u003Cstrong>Reach for \u003Ccode>key\u003C\u002Fcode> when a prop change should mean &quot;this is now a different thing,&quot; not just &quot;new data.&quot;\u003C\u002Fstrong> A conversation id, a selected record&#39;s id, a form you want fully reset between edits — these are \u003Ccode>key\u003C\u002Fcode> candidates.","\u003Cstrong>Don&#39;t sync state to a prop in an effect just to reset it.\u003C\u002Fstrong> A changing \u003Ccode>key\u003C\u002Fcode> gets you there in one render via the initializer, instead of a state update chasing the prop one render behind.","\u003Cstrong>Give every list item a stable, unique \u003Ccode>key\u003C\u002Fcode> drawn from the data, never the array index\u003C\u002Fstrong>, unless the list is provably static and never reorders or filters.","\u003Cstrong>Don&#39;t use a remount as a substitute for correct cleanup.\u003C\u002Fstrong> 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.","\u003Cstrong>Debugging a state-reset surprise? Ask which slot the component sits in, and whether its type or key changed there\u003C\u002Fstrong> — faster than reading through the component&#39;s own logic.",{"id":323,"html":324,"type":135},"b50","\u003C!-- playground:start -->",{"id":326,"html":327,"text":327,"type":149,"level":31},"b51","🎮 Try it yourself",{"id":329,"html":330,"type":135},"b52","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-rerender-vs-remount\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":332,"html":333,"type":135},"b53","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":335,"html":336,"type":135},"b54","\u003C!-- playground:end -->",{"id":338,"html":339,"text":339,"type":149,"level":31},"b55","FAQ",{"id":341,"html":342,"text":342,"type":149,"level":43},"b56","Does changing a prop cause a React component to remount?",{"id":344,"html":345,"type":135},"b57","\u003Cp>No, not by itself. A prop change flows into the same instance as a re-render, as long as the component&#39;s type and \u003Ccode>key\u003C\u002Fcode> at that tree position stay the same. Only a type change, a \u003Ccode>key\u003C\u002Fcode> change, or the element leaving and re-entering the tree causes a remount.\u003C\u002Fp>",{"id":347,"html":348,"text":349,"type":149,"level":43},"b58","What does changing the \u003Ccode>key\u003C\u002Fcode> prop actually do?","What does changing the key prop actually do?",{"id":351,"html":352,"type":135},"b59","\u003Cp>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.\u003C\u002Fp>",{"id":354,"html":355,"text":356,"type":149,"level":43},"b60","Why does my component&#39;s state reset when I didn&#39;t expect it to?","Why does my component's state reset when I didn't expect it to?",{"id":358,"html":359,"type":135},"b61","\u003Cp>The most common causes are: a \u003Ccode>key\u003C\u002Fcode> that&#39;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 \u003Ccode>key\u003C\u002Fcode> than it had on the previous render because of an unstable index-based key.\u003C\u002Fp>",{"id":361,"html":362,"text":363,"type":149,"level":43},"b62","Is StrictMode&#39;s double effect invocation a remount?","Is StrictMode's double effect invocation a remount?",{"id":365,"html":366,"type":135},"b63","\u003Cp>It&#39;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&#39;t triggered by the type\u002Fposition\u002Fkey rule — it happens regardless, purely to test your cleanup.\u003C\u002Fp>",{"id":368,"html":369,"text":369,"type":149,"level":43},"b64","Does the React Compiler change when a component remounts?",{"id":371,"html":372,"type":135},"b65","\u003Cp>No. The compiler&#39;s job is deciding whether a re-render can be skipped through automatic memoization; it doesn&#39;t touch the reconciliation rule that decides re-render versus remount. A \u003Ccode>key\u003C\u002Fcode> change forces a remount the same way whether or not the compiler is enabled.\u003C\u002Fp>",{"id":374,"html":375,"type":135},"b66","\u003C!-- quiz:start -->",{"id":377,"html":378,"text":378,"type":149,"level":31},"b67","🧠 Test yourself",{"id":380,"html":381,"type":135},"b68","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-rerender-vs-remount\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":383,"html":384,"type":135},"b69","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":386,"html":387,"type":135},"b70","\u003C!-- quiz:end -->",{"id":389,"html":390,"text":390,"type":149,"level":31},"b71","Cheat sheet",{"id":392,"head":393,"rows":397,"type":428},"b72",[394,395,396],"Situation","Re-render or remount?","Why",[398,402,406,409,413,417,421,424],[399,400,401],"Parent passes new props, same type &amp; key, same slot","Re-render","React reuses the instance; only props change",[403,404,405],"\u003Ccode>key\u003C\u002Fcode> changes on the same type at the same slot","Remount","\u003Ccode>key\u003C\u002Fcode> overrides type\u002Fposition identity",[407,404,408],"Type changes at the same slot (\u003Ccode>isX ? &lt;A\u002F&gt; : &lt;B\u002F&gt;\u003C\u002Fcode>)","Different type ⇒ React assumes a different subtree",[410,411,412],"\u003Ccode>{cond &amp;&amp; &lt;Modal\u002F&gt;}\u003C\u002Fcode> toggling \u003Ccode>cond\u003C\u002Fcode>","Mount ↔ unmount","The element only exists in the tree while \u003Ccode>cond\u003C\u002Fcode> is true",[414,415,416],"List item reordered with a stable \u003Ccode>id\u003C\u002Fcode> key","Re-render (instance moves with it)","Identity travels with the key, not the position",[418,419,420],"List item reordered with an \u003Cstrong>index\u003C\u002Fstrong> key","Effectively a remount\u002Fmixup down the list","The index, not the item, is the identity",[422,400,423],"\u003Ccode>setState\u003C\u002Fcode> called inside the component","Same instance, new state, same identity",[425,426,427],"Ancestor unmounts","All descendants unmount","No slot survives without its parent","table",{"id":430,"code":431,"type":194,"language":195,"highlight":432},"b73","\u002F\u002F Force a fresh instance on purpose — the documented pattern for\n\u002F\u002F \"reset this component's state when X changes\":\n\u003CChat key={conversationId} conversationId={conversationId} \u002F>\n\n\u002F\u002F Don't do this instead — it re-renders once with stale state,\n\u002F\u002F then a second time with the reset applied:\nuseEffect(() => setDraft(''), [conversationId]);\n\n\u002F\u002F Lists: key by stable identity, never by position.\n{items.map((item) => \u003CRow key={item.id} item={item} \u002F>)}",[],{"id":434,"html":435,"text":435,"type":149,"level":31},"b74","Key takeaways",{"id":437,"type":155,"items":438,"ordered":18},"b75",[439,440,441,442,443,444],"State belongs to a \u003Cstrong>(type, key) pair at a tree position\u003C\u002Fstrong>, not to your component function — that&#39;s the one rule behind every re-render\u002Fremount surprise.","\u003Cstrong>Re-render\u003C\u002Fstrong>: same instance, new props, existing state and effect subscriptions survive.","\u003Cstrong>Remount\u003C\u002Fstrong>: old instance torn down (cleanup runs, state discarded), new instance built from scratch.","\u003Cstrong>\u003Ccode>key\u003C\u002Fcode> is your override\u003C\u002Fstrong> — change it to force a remount on purpose instead of syncing state in an effect.","\u003Cstrong>List keys are identity, not decoration.\u003C\u002Fstrong> An unstable key produces exactly the &quot;wrong item&#39;s state&quot; bugs that a good key prevents.","StrictMode&#39;s double-invoke and the React Compiler&#39;s memoization both sit \u003Cem>alongside\u003C\u002Fem> this rule — neither one changes it.",{"id":446,"html":447,"text":447,"type":149,"level":31},"b76","Back to that chat draft",{"id":449,"html":450,"type":135},"b77","\u003Cp>The draft didn&#39;t clear because \u003Ccode>Chat\u003C\u002Fcode> never stopped being the same \u003Ccode>Chat\u003C\u002Fcode> — React had no reason to think otherwise, because nothing about its type or its position (or, before the fix, its key) ever changed. Adding \u003Ccode>key={conversationId}\u003C\u002Fcode> didn&#39;t patch around the bug; it told React the one thing it needed to know: a different conversation is a different \u003Ccode>Chat\u003C\u002Fcode>, and it should be built fresh.\u003C\u002Fp>",{"id":452,"html":453,"type":135},"b78","\u003Cp>The next time state resets when you didn&#39;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&#39;ll usually have the answer before you&#39;ve read a single line of the component&#39;s own code.\u003C\u002Fp>",{"id":455,"html":456,"type":135},"b79","\u003Cp>What&#39;s the state-reset bug that cost you the most time to track down — and did it turn out to be a \u003Ccode>key\u003C\u002Fcode>?\u003C\u002Fp>",{"id":458,"type":459},"b80","divider",{"id":461,"html":462,"type":135},"b81","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":464,"html":465,"type":135},"b82","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":467,"type":155,"items":468,"ordered":18},"b83",[469,470,471],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You switch from one conversation to another in a chat app. Same `\u003CChat \u002F>` 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.\n\nNothing 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.\n\nThis 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.\n\nThis 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.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Tell, from the outside, whether a state reset you're seeing was a re-render or a remount\n- Explain the actual rule React uses to decide — type and position in the tree, with `key` as an override\n- Predict when a conditional (`{cond && \u003CA\u002F>}`, a ternary, a list without stable keys) causes a hidden remount\n- Force a remount **on purpose** with `key`, instead of syncing state in an effect\n- Tell React's deliberate StrictMode double-invoke and the React Compiler apart from a real remount\n\n## Who this is for\n\nYou'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.\n\n## Table of contents\n\n- [The problem: a draft that won't go away](#the-problem-a-draft-that-wont-go-away)\n- [The mental model: state lives on a slot in the tree, not on your component](#the-mental-model-state-lives-on-a-slot-in-the-tree-not-on-your-component)\n- [Stage 1: same type, same slot — a re-render](#stage-1-same-type-same-slot--a-re-render)\n- [Stage 2: different type, same slot — a remount](#stage-2-different-type-same-slot--a-remount)\n- [Stage 3: same type, different `key` — a remount on purpose](#stage-3-same-type-different-key--a-remount-on-purpose)\n- [Stage 4: lists, and why an unstable `key` causes accidental remounts](#stage-4-lists-and-why-an-unstable-key-causes-accidental-remounts)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices: choosing on purpose](#best-practices-choosing-on-purpose)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n\n## The problem: a draft that won't go away\n\nHere's the chat component, written the way most people write it first:\n\n```jsx\nfunction Chat({ conversationId, contactName }) {\n  const [draft, setDraft] = useState('');\n\n  useEffect(() => {\n    \u002F\u002F subscribe to messages for conversationId\n  }, [conversationId]);\n\n  return (\n    \u003Cdiv>\n      \u003Ch3>Chat with {contactName}\u003C\u002Fh3>\n      \u003CMessageList conversationId={conversationId} \u002F>\n      \u003Ctextarea\n        value={draft}\n        onChange={(e) => setDraft(e.target.value)}\n        placeholder=\"Type a message…\"\n      \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction App() {\n  const [activeId, setActiveId] = useState('alice');\n  const contact = CONTACTS[activeId];\n  return (\n    \u003C>\n      \u003CContactList onSelect={setActiveId} \u002F>\n      \u003CChat conversationId={activeId} contactName={contact.name} \u002F>\n    \u003C\u002F>\n  );\n}\n```\n\nClick 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.\n\nThe instinct is to \"fix\" this by syncing state in an effect:\n\n```jsx\n\u002F\u002F ❌ works, but for the wrong reason, and it costs an extra render\nuseEffect(() => {\n  setDraft('');\n}, [conversationId]);\n```\n\nThis 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.\n\n## The mental model: state lives on a slot in the tree, not on your component\n\nHere's the sentence that resolves the chat bug and most others like it:\n\n> **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.\n\n\"Re-render\" and \"remount\" are the two outcomes of that decision:\n\n- **Re-render**: same instance, new props flow in, existing hooks keep their state, effects re-run only if their dependencies changed.\n- **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).\n\n`Chat` never remounted. Its slot — inside `App`, right after `ContactList` — held a `\u003CChat>` 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.\n\n**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.\n\n## Stage 1: same type, same slot — a re-render\n\nThis is the default, and it's why props updating in place feels unremarkable:\n\n```jsx\nfunction Counter({ label }) {\n  const [count, setCount] = useState(0);\n  return (\n    \u003Cbutton onClick={() => setCount((c) => c + 1)}>\n      {label}: {count}\n    \u003C\u002Fbutton>\n  );\n}\n\nfunction App({ label }) {\n  return \u003CCounter label={label} \u002F>; \u002F\u002F same slot, same type, every render\n}\n```\n\nWhatever 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.\n\n## Stage 2: different type, same slot — a remount\n\nNow put two different component types in the same slot, switched by a condition:\n\n```jsx\nfunction AuthGate({ isLoggedIn }) {\n  return isLoggedIn ? \u003CDashboard \u002F> : \u003CLoginForm \u002F>;\n}\n```\n\nEvery time `isLoggedIn` flips, the slot inside `AuthGate` holds a different *type* — `Dashboard` one render, `LoginForm` the next. React doesn't try to reconcile a `\u003Cbutton>` inside `LoginForm` against a `\u003Cbutton>` 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.\n\nThis 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.\n\n**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.\n\n## Stage 3: same type, different `key` — a remount on purpose\n\nBack 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 `key`s differ. That's the tool for the chat bug:\n\n```jsx\n\u002F\u002F ✅ key makes conversationId part of the instance's identity\n\u003CChat key={activeId} conversationId={activeId} contactName={contact.name} \u002F>\n```\n\nNow 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`.\n\nThis 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.\n\n## Stage 4: lists, and why an unstable `key` causes accidental remounts\n\n`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:\n\n```jsx\n\u002F\u002F ❌ index as key: correct-looking, quietly wrong once the list reorders\n{todos.map((todo, i) => (\n  \u003CTodoRow key={i} todo={todo} \u002F>\n))}\n\n\u002F\u002F ✅ a stable id that travels with the item\n{todos.map((todo) => (\n  \u003CTodoRow key={todo.id} todo={todo} \u002F>\n))}\n```\n\nWith 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.\n\n**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.\n\n## Edge cases and gotchas\n\n- **A conditional render is a mount\u002Funmount transition, not a hidden re-render.** `{isOpen && \u003CModal \u002F>}` mounts `Modal` fresh every time `isOpen` flips true — there's no instance to preserve across the `false` in between.\n- **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.\n- **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.\n- **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.\n- **The React Compiler only changes whether a re-render is skipped**, via automatic memoization. It doesn't touch the type\u002Fposition\u002Fkey identity rule — a `key` change still forces a remount the same way with or without it.\n\n## Best practices: choosing on purpose\n\n- **Reach for `key` when 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 are `key` candidates.\n- **Don't sync state to a prop in an effect just to reset it.** A changing `key` gets you there in one render via the initializer, instead of a state update chasing the prop one render behind.\n- **Give every list item a stable, unique `key` drawn from the data, never the array index**, unless the list is provably static and never reorders or filters.\n- **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.\n- **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.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-rerender-vs-remount\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## FAQ\n\n### Does changing a prop cause a React component to remount?\n\nNo, 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.\n\n### What does changing the `key` prop actually do?\n\nIt 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.\n\n### Why does my component's state reset when I didn't expect it to?\n\nThe 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.\n\n### Is StrictMode's double effect invocation a remount?\n\nIt'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\u002Fposition\u002Fkey rule — it happens regardless, purely to test your cleanup.\n\n### Does the React Compiler change when a component remounts?\n\nNo. 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.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-rerender-vs-remount\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## Cheat sheet\n\n| Situation | Re-render or remount? | Why |\n| --- | --- | --- |\n| Parent passes new props, same type & key, same slot | Re-render | React reuses the instance; only props change |\n| `key` changes on the same type at the same slot | Remount | `key` overrides type\u002Fposition identity |\n| Type changes at the same slot (`isX ? \u003CA\u002F> : \u003CB\u002F>`) | Remount | Different type ⇒ React assumes a different subtree |\n| `{cond && \u003CModal\u002F>}` toggling `cond` | Mount ↔ unmount | The element only exists in the tree while `cond` is true |\n| List item reordered with a stable `id` key | Re-render (instance moves with it) | Identity travels with the key, not the position |\n| List item reordered with an **index** key | Effectively a remount\u002Fmixup down the list | The index, not the item, is the identity |\n| `setState` called inside the component | Re-render | Same instance, new state, same identity |\n| Ancestor unmounts | All descendants unmount | No slot survives without its parent |\n\n```jsx\n\u002F\u002F Force a fresh instance on purpose — the documented pattern for\n\u002F\u002F \"reset this component's state when X changes\":\n\u003CChat key={conversationId} conversationId={conversationId} \u002F>\n\n\u002F\u002F Don't do this instead — it re-renders once with stale state,\n\u002F\u002F then a second time with the reset applied:\nuseEffect(() => setDraft(''), [conversationId]);\n\n\u002F\u002F Lists: key by stable identity, never by position.\n{items.map((item) => \u003CRow key={item.id} item={item} \u002F>)}\n```\n\n## Key takeaways\n\n- 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\u002Fremount surprise.\n- **Re-render**: same instance, new props, existing state and effect subscriptions survive.\n- **Remount**: old instance torn down (cleanup runs, state discarded), new instance built from scratch.\n- **`key` is your override** — change it to force a remount on purpose instead of syncing state in an effect.\n- **List keys are identity, not decoration.** An unstable key produces exactly the \"wrong item's state\" bugs that a good key prevents.\n- StrictMode's double-invoke and the React Compiler's memoization both sit *alongside* this rule — neither one changes it.\n\n## Back to that chat draft\n\nThe 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.\n\nThe 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.\n\nWhat's the state-reset bug that cost you the most time to track down — and did it turn out to be a `key`?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":108,"canonical":474,"description":109},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-rerender-vs-remount","01a04446-373a-775b-8aaf-ba48747e229a",{"id":477,"locked":18},"01a04446-3764-776d-80d9-988635d39095",[479],{"id":45,"slug":46,"title":48,"_count":480},{"questions":51},[482],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":484,"questionCount":51},{"questions":51}]