[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-react-weekly-compiler-memoization":44,"search-suggestions":60,"quiz-article-react-weekly-compiler-memoization":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},"01a0632c-72a3-71f4-8bea-318b4b6711fb","react-weekly-compiler-memoization","PRACTICE_QUIZ","React Compiler & Memoization","Test what you just learned about React's default re-render behavior, what React Compiler 1.0 automates, and where useMemo\u002FuseCallback still earn their place.",{"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,85,88,92,96,100,103],{"slug":62,"name":63,"articles":64},"webdev","Webdev",81,{"slug":66,"name":67,"articles":68},"javascript","Javascript",68,{"slug":70,"name":71,"articles":72},"frontend","Frontend",67,{"slug":74,"name":75,"articles":76},"css","Css",29,{"slug":78,"name":79,"articles":80},"tutorial","Tutorial",17,{"slug":82,"name":83,"articles":84},"typescript","Typescript",11,{"slug":86,"name":87,"articles":84},"performance","Performance",{"slug":89,"name":90,"articles":91},"react","React",9,{"slug":93,"name":94,"articles":95},"grammar","Grammar",6,{"slug":97,"name":98,"articles":99},"html","Html",5,{"slug":101,"name":102,"articles":99},"node","Node",{"slug":104,"name":105,"articles":99},"browser","Browser",{"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":457,"seo":458,"translationGroupId":460,"series":461,"podcastUrl":52,"verticalId":5,"thread":469,"assessments":471,"translations":474,"quiz":476},"01a0632c-7248-73e3-9951-e8c6eaa578c7","React Compiler 1.0: What useMemo You Can Delete","React Compiler 1.0 is stable. Here's the re-render model it automates, which useMemo\u002FuseCallback\u002Fmemo calls you can delete, and what still needs you.","\u002Fmedia\u002Fcovers\u002Freact-weekly-compiler-memoization.png",13,"2026-09-05T10:17:49.849Z",50,{"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":62,"name":63,"color":52},{"slug":66,"name":67,"color":52},{"slug":89,"name":90,"color":52},{"slug":78,"name":79,"color":52},{"assessments":127},1,{"slug":46,"title":129},"React Compiler & memoization — interactive playground",{"blocks":131,"version":127},[132,136,139,142,147,150,159,162,165,168,182,186,189,195,198,201,205,208,211,214,217,220,223,226,229,232,235,238,241,244,247,250,253,256,259,262,266,269,273,276,281,284,287,292,295,298,301,309,312,320,323,326,329,332,335,338,341,344,347,350,353,356,359,362,407,411,414,417,420,423,426,429,436,439,442,445,448,451],{"id":133,"html":134,"type":135},"b1","\u003Cp>Open any React codebase built before late 2025 and you&#39;ll find the same defensive scaffolding in nearly every component: a \u003Ccode>memo()\u003C\u002Fcode> wrapper here, a \u003Ccode>useCallback\u003C\u002Fcode> there, a \u003Ccode>useMemo\u003C\u002Fcode> around a sort you were never quite sure was expensive enough to justify it. Most of that code was never a response to a measured problem — it was insurance against a re-render you were \u003Cem>guessing\u003C\u002Fem> might happen.\u003C\u002Fp>","paragraph",{"id":137,"html":138,"type":135},"b2","\u003Cp>As of \u003Cstrong>React Compiler 1.0\u003C\u002Fstrong>, stable since October 2025, that guessing game is mostly over. The compiler does the same analysis you were doing by hand, applies it more precisely than hooks alone can, and in a few cases does things manual memoization structurally cannot do at all. This is episode two of \u003Cstrong>React Deep Dive\u003C\u002Fstrong>, and it&#39;s about what &quot;automatic memoization&quot; actually means, what it removes from your code, and what it deliberately leaves for you to keep deciding.\u003C\u002Fp>",{"id":140,"html":141,"type":135},"b3","\u003Cp>This article is written against \u003Cstrong>React 19.2\u003C\u002Fstrong> (verified 19.2.8, the current npm release as of this writing) and \u003Cstrong>React Compiler 1.0\u003C\u002Fstrong> (the stable \u003Ccode>babel-plugin-react-compiler@1.0.0\u003C\u002Fcode> release, shipped October 7, 2025 at React Conf — verified against React&#39;s own release notes). If you want the re-render vocabulary this article leans on, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Freact-re-render-vs-remount-what-actually-triggers-each-5fok\">episode one covered re-render vs. remount\u003C\u002Fa> — useful background, not required reading.\u003C\u002Fp>",{"id":143,"html":144,"text":145,"type":146,"level":31},"b4","What you&#39;ll learn","What you'll learn","heading",{"id":148,"html":149,"type":135},"b5","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":151,"type":152,"items":153,"ordered":18},"b6","list",[154,155,156,157,158],"Explain \u003Cem>why\u003C\u002Fem> React re-renders a whole subtree by default, and what memoization actually buys you when you add it","Read code with \u003Ccode>React.memo\u003C\u002Fcode>, \u003Ccode>useMemo\u003C\u002Fcode>, and \u003Ccode>useCallback\u003C\u002Fcode> and know exactly what problem each one was solving","Describe what React Compiler automates, including two cases manual hooks can&#39;t solve at all","Know when you still need \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> even with the compiler installed","Add the compiler to a project and read its lint diagnostics when it can&#39;t safely optimize something",{"id":160,"html":161,"text":161,"type":146,"level":31},"b7","Who this is for",{"id":163,"html":164,"type":135},"b8","\u003Cp>You&#39;ve written function components and used \u003Ccode>useState\u003C\u002Fcode>, \u003Ccode>useEffect\u003C\u002Fcode>, and at least one of \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode>\u002F\u003Ccode>React.memo\u003C\u002Fcode> before, even if you couldn&#39;t fully explain why. No compiler internals or build-tooling experience required.\u003C\u002Fp>",{"id":166,"html":167,"text":167,"type":146,"level":31},"b9","Table of contents",{"id":169,"type":152,"items":170,"ordered":18},"b10",[171,172,173,174,175,176,177,178,179,180,181],"\u003Ca href=\"#the-problem-manual-memoization-doesnt-scale\">The problem: manual memoization doesn&#39;t scale\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-what-memoization-actually-buys-you\">The mental model: what memoization actually buys you\u003C\u002Fa>","\u003Ca href=\"#stage-1-the-re-render-without-memoization\">Stage 1: the re-render, without memoization\u003C\u002Fa>","\u003Ca href=\"#stage-2-the-manual-fix-and-its-subtle-crack\">Stage 2: the manual fix, and its subtle crack\u003C\u002Fa>","\u003Ca href=\"#stage-3-the-same-code-compiled\">Stage 3: the same code, compiled\u003C\u002Fa>","\u003Ca href=\"#stage-4-turning-it-on\">Stage 4: turning it on\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key takeaways\u003C\u002Fa>",{"id":183,"html":184,"text":185,"type":146,"level":31},"b11","The problem: manual memoization doesn&#39;t scale","The problem: manual memoization doesn't scale",{"id":187,"html":188,"type":135},"b12","\u003Cp>Here&#39;s a dashboard with a search box and a team roster underneath it. Nothing exotic:\u003C\u002Fp>",{"id":190,"code":191,"type":192,"language":193,"highlight":194},"b13","function Dashboard({ members }) {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={(e) => setQuery(e.target.value)} \u002F>\n      \u003CTeamRoster members={members} onInvite={(id) => sendInvite(id)} \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction TeamRoster({ members, onInvite }) {\n  const sorted = sortByActivity(members); \u002F\u002F recomputed on every call\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => onInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n}","code","jsx",[],{"id":196,"html":197,"type":135},"b14","\u003Cp>\u003Ccode>members\u003C\u002Fcode> never changes while you type. But every keystroke updates \u003Ccode>query\u003C\u002Fcode>, which re-renders \u003Ccode>Dashboard\u003C\u002Fcode>, which re-renders \u003Ccode>TeamRoster\u003C\u002Fcode> — by default, with no memoization applied anywhere, React re-renders a component and everything below it whenever the component&#39;s own state or a parent&#39;s state changes. \u003Ccode>TeamRoster\u003C\u002Fcode> re-runs \u003Ccode>sortByActivity\u003C\u002Fcode> from scratch, builds a brand-new array, and hands every \u003Ccode>MemberCard\u003C\u002Fcode> a brand-new \u003Ccode>onInvite\u003C\u002Fcode> closure. Every card re-renders, on every keystroke, for a value that didn&#39;t change.\u003C\u002Fp>",{"id":199,"html":200,"type":135},"b15","\u003Cp>The textbook fix is to memoize the boundary:\u003C\u002Fp>",{"id":202,"code":203,"type":192,"language":193,"highlight":204},"b16","const TeamRoster = memo(function TeamRoster({ members, onInvite }) {\n  const sorted = useMemo(() => sortByActivity(members), [members]);\n  const handleInvite = useCallback((id) => onInvite(id), [onInvite]);\n\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => handleInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n});",[],{"id":206,"html":207,"type":135},"b17","\u003Cp>This helps — \u003Ccode>sortByActivity\u003C\u002Fcode> only re-runs when \u003Ccode>members\u003C\u002Fcode> actually changes. But look closely at the line that renders each card: \u003Ccode>onInvite={() =&gt; handleInvite(m.id)}\u003C\u002Fcode>. That inline arrow function is created fresh on every render, \u003Ccode>useCallback\u003C\u002Fcode> wrapper or not, so \u003Ccode>MemberCard\u003C\u002Fcode> still gets a new \u003Ccode>onInvite\u003C\u002Fcode> prop every time and still re-renders. \u003Ccode>useCallback\u003C\u002Fcode> cannot fix this without restructuring the code — hooks can only stabilize \u003Cem>values\u003C\u002Fem>, and the value here is the arrow function \u003Cem>around\u003C\u002Fem> \u003Ccode>handleInvite\u003C\u002Fcode>, not \u003Ccode>handleInvite\u003C\u002Fcode> itself.\u003C\u002Fp>",{"id":209,"html":210,"type":135},"b18","\u003Cp>This is the actual shape of the problem: correct manual memoization requires you to trace every value that flows into every child, on every edit, forever. Miss one spot — and the spot above is easy to miss — and the memoization you added silently does nothing.\u003C\u002Fp>",{"id":212,"html":213,"text":213,"type":146,"level":31},"b19","The mental model: what memoization actually buys you",{"id":215,"html":216,"type":135},"b20","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> React re-renders a component whenever its own state changes \u003Cem>and\u003C\u002Fem> whenever its parent re-renders — regardless of whether the props it receives actually changed. Memoization doesn&#39;t stop a component from re-rendering because of its own state; it gives React a way to tell that a \u003Cem>child&#39;s\u003C\u002Fem> inputs didn&#39;t change, so React&#39;s reconciler can skip that child&#39;s subtree entirely rather than re-run it and diff the result.\u003C\u002Fp>",{"id":218,"html":219,"type":135},"b21","\u003Cp>Concretely: if a component returns the exact same element reference on two consecutive renders (not just equal-looking JSX, the same object in memory), React bails out of that subtree without touching it. \u003Ccode>React.memo\u003C\u002Fcode> gets you this by comparing props before re-rendering the child; \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> get you this by keeping the \u003Cem>values passed into\u003C\u002Fem> JSX stable, so the JSX built from them stays stable too.\u003C\u002Fp>",{"id":221,"html":222,"type":135},"b22","\u003Cp>React Compiler&#39;s entire job is producing that same stability automatically, everywhere it&#39;s provably safe to do so — by reading your component&#39;s code and figuring out, per value, whether it could have changed since the last render. It doesn&#39;t change \u003Cem>when\u003C\u002Fem> your component&#39;s own state causes it to re-render. It changes whether that re-render cascades into components that had nothing to do with the change.\u003C\u002Fp>",{"id":224,"html":225,"text":225,"type":146,"level":31},"b23","Stage 1: the re-render, without memoization",{"id":227,"html":228,"type":135},"b24","\u003Cp>Run the \u003Ccode>Dashboard\u003C\u002Fcode>\u002F\u003Ccode>TeamRoster\u003C\u002Fcode> code above and every \u003Ccode>MemberCard\u003C\u002Fcode> logs a render on every keystroke — you can watch this happen for real in the playground below, which runs the actual React 19.2.8 runtime, not a simulation. This is React doing exactly what it&#39;s documented to do: no memoization means no bailout, so the whole subtree re-runs.\u003C\u002Fp>",{"id":230,"html":231,"type":135},"b25","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> an unmemoized re-render isn&#39;t a bug. It&#39;s React&#39;s default, and it&#39;s usually fine — React is fast enough that most re-renders never cost anything a user would notice. The problem only shows up when a subtree is expensive enough, or large enough, that redoing it on every keystroke becomes visible.\u003C\u002Fp>",{"id":233,"html":234,"type":135},"b26","\u003C!-- playground:start -->",{"id":236,"html":237,"text":237,"type":146,"level":31},"b27","🎮 Try it yourself",{"id":239,"html":240,"type":135},"b28","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-compiler-memoization\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":242,"html":243,"type":135},"b29","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":245,"html":246,"type":135},"b30","\u003C!-- playground:end -->",{"id":248,"html":249,"text":249,"type":146,"level":31},"b31","Stage 2: the manual fix, and its subtle crack",{"id":251,"html":252,"type":135},"b32","\u003Cp>The \u003Ccode>memo\u003C\u002Fcode>\u002F\u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> version above fixes the expensive sort but not the inline arrow, for a structural reason worth sitting with: hooks can only be called unconditionally, at the top level of a component, so you can&#39;t \u003Ccode>useCallback\u003C\u002Fcode> a function that&#39;s constructed \u003Cem>inside\u003C\u002Fem> a \u003Ccode>.map()\u003C\u002Fcode> callback per item — you&#39;d be calling a hook in a loop, which breaks the Rules of Hooks. The only fix within hooks alone is to restructure the code, usually by pushing the click handler down into \u003Ccode>MemberCard\u003C\u002Fcode> and passing just the id.\u003C\u002Fp>",{"id":254,"html":255,"type":135},"b33","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> manual memoization isn&#39;t just tedious, it has real structural gaps — situations no combination of \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode>\u002F\u003Ccode>React.memo\u003C\u002Fcode> can close without changing how the code is written. That&#39;s the opening React Compiler was built to close.\u003C\u002Fp>",{"id":257,"html":258,"text":258,"type":146,"level":31},"b34","Stage 3: the same code, compiled",{"id":260,"html":261,"type":135},"b35","\u003Cp>With React Compiler enabled, you write the \u003Cem>first\u003C\u002Fem> version of \u003Ccode>TeamRoster\u003C\u002Fcode> — no \u003Ccode>memo\u003C\u002Fcode>, no \u003Ccode>useMemo\u003C\u002Fcode>, no \u003Ccode>useCallback\u003C\u002Fcode>, the inline arrow left exactly where it reads most naturally:\u003C\u002Fp>",{"id":263,"code":264,"type":192,"language":193,"highlight":265},"b36","function TeamRoster({ members, onInvite }) {\n  const sorted = sortByActivity(members);\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => onInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n}",[],{"id":267,"html":268,"type":135},"b37","\u003Cp>The compiler analyzes the function body at build time and rewrites it — roughly, generating memoized slots for \u003Ccode>sorted\u003C\u002Fcode> and for each card&#39;s element, comparing them against the previous render&#39;s values, and reusing the old result when nothing relevant changed. According to React&#39;s own compiler documentation, this handles the inline-arrow case correctly \u003Cem>with or without\u003C\u002Fem> the arrow function, because the compiler is reasoning about the whole function&#39;s data flow, not applying a hook to a single named value.\u003C\u002Fp>",{"id":270,"html":271,"type":272},"b38","\u003Cp>This part is a \u003Cstrong>model\u003C\u002Fstrong>, not a live demo: React Compiler is a build-time Babel transform, so it cannot run inside a static HTML page without a bundler. The playground above shows the \u003Cem>real, measurable effect\u003C\u002Fem> — fewer re-renders — that flipping &quot;apply manual memoization&quot; produces with the actual React runtime; the compiled output is the same effect, generated for you, from the plain code shown here.\u003C\u002Fp>\n","quote",{"id":274,"html":275,"type":135},"b39","\u003Cp>Two things the compiler can do that manual hooks structurally can&#39;t, both documented in its own release notes:\u003C\u002Fp>",{"id":277,"type":152,"items":278,"ordered":18},"b40",[279,280],"\u003Cstrong>Memoize after an early return.\u003C\u002Fstrong> \u003Ccode>useMemo\u003C\u002Fcode> and \u003Ccode>useCallback\u003C\u002Fcode> can&#39;t appear after a conditional \u003Ccode>return\u003C\u002Fcode> — that&#39;s the Rules of Hooks. The compiler isn&#39;t a hook, so it can memoize values computed after one.","\u003Cstrong>Handle the inline-arrow case above\u003C\u002Fstrong> without you restructuring anything.",{"id":282,"html":283,"text":283,"type":146,"level":31},"b41","Stage 4: turning it on",{"id":285,"html":286,"type":135},"b42","\u003Cp>Installing it is a dev dependency plus a build-tool integration:\u003C\u002Fp>",{"id":288,"code":289,"type":192,"language":290,"highlight":291},"b43","npm install --save-dev --save-exact babel-plugin-react-compiler@latest\nnpm install --save-dev eslint-plugin-react-hooks@latest","bash",[],{"id":293,"html":294,"type":135},"b44","\u003Cp>\u003Ccode>eslint-plugin-react-hooks\u003C\u002Fcode>&#39;s \u003Ccode>recommended\u003C\u002Fcode> and \u003Ccode>recommended-latest\u003C\u002Fcode> presets now ship the compiler&#39;s lint rules directly — this replaced the separate \u003Ccode>eslint-plugin-react-compiler\u003C\u002Fcode> package when the compiler went stable, and the lint rules work even in projects that haven&#39;t added the compiler itself yet, because they&#39;re really flagging Rules-of-React violations.\u003C\u002Fp>",{"id":296,"html":297,"type":135},"b45","\u003Cp>New projects scaffolded with recent versions of Vite, Next.js (15.3.1+), or Expo (SDK 54+) can start with the compiler already wired in. Existing codebases adopt it incrementally: point the compiler at one directory or route first, watch the lint output, and expand from there.\u003C\u002Fp>",{"id":299,"html":300,"text":300,"type":146,"level":31},"b46","Edge cases and gotchas",{"id":302,"type":152,"items":303,"ordered":18},"b47",[304,305,306,307,308],"\u003Cstrong>It only compiles components and hooks — not arbitrary functions.\u003C\u002Fstrong> A plain helper function called from inside a component gets memoized as a \u003Cem>call\u003C\u002Fem>, but if that same expensive helper is called from three different components, each one still pays the cost independently; the compiler&#39;s memoization isn&#39;t shared across components.","\u003Cstrong>It requires the Rules of React.\u003C\u002Fstrong> The compiler assumes your components and hooks are pure — idempotent given the same props\u002Fstate, no mutating props or state during render, no side effects in render. Code that breaks these rules in ways the compiler can statically detect gets flagged (surfaced through \u003Ccode>eslint-plugin-react-hooks\u003C\u002Fcode>, with rules like \u003Ccode>set-state-in-render\u003C\u002Fcode> and \u003Ccode>set-state-in-effect\u003C\u002Fcode>); code that breaks them in ways JavaScript can&#39;t statically catch may compile without warning and behave subtly differently than before.","\u003Cstrong>Removing existing manual memoization isn&#39;t automatically safe.\u003C\u002Fstrong> React&#39;s own guidance is to leave existing \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> calls in place, or test carefully before deleting them — the compiler&#39;s memoization boundaries won&#39;t always land in exactly the same places yours did, and if some effect elsewhere depends on one of your values staying referentially stable across specific renders, changing that boundary can change how often that effect fires.","\u003Cstrong>React 17 and 18 are supported, not just 19\u003C\u002Fstrong> — with a \u003Ccode>target\u003C\u002Fcode> config option and the \u003Ccode>react-compiler-runtime\u003C\u002Fcode> package as an added dependency. On React 19 neither is needed.","\u003Cstrong>It&#39;s a build-time transform, full stop.\u003C\u002Fstrong> There&#39;s no runtime flag or devtools toggle; if it isn&#39;t wired into your bundler&#39;s config, none of this applies to your app.",{"id":310,"html":311,"text":311,"type":146,"level":31},"b48","Best practices",{"id":313,"type":152,"items":314,"ordered":18},"b49",[315,316,317,318,319],"\u003Cstrong>New code: stop hand-memoizing by default.\u003C\u002Fstrong> Write the plain version. Reach for \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> only when you need explicit control over a value&#39;s identity — most commonly, when that value is a dependency of an effect and you need to guarantee it won&#39;t cause the effect to over-fire.","\u003Cstrong>Turn on the compiler-powered lint rules even before you install the compiler.\u003C\u002Fstrong> They&#39;re Rules-of-React checks, and they catch real bugs (state updates during render, unsafe ref reads) independent of whether you&#39;ve adopted the compiler yet.","\u003Cstrong>Adopt incrementally in an existing codebase.\u003C\u002Fstrong> Compile one route or directory, watch for lint diagnostics and behavior regressions, then widen the scope. Pin the compiler to an exact version (\u003Ccode>--save-exact\u003C\u002Fcode>) rather than a semver range if your test coverage is thin, since future versions may change memoization boundaries.","\u003Cstrong>Don&#39;t reach for the compiler to fix a slow function.\u003C\u002Fstrong> If \u003Ccode>sortByActivity\u003C\u002Fcode> above were genuinely expensive, calling it from several components would still re-run it in each one — profile first, and consider your own caching if the same expensive call is duplicated across the tree.","\u003Cstrong>Use \u003Ccode>&quot;use no memo&quot;\u003C\u002Fcode> as a scalpel, not a habit.\u003C\u002Fstrong> It opts one function out of compilation, useful while debugging a compiler diagnostic or isolating code the compiler can&#39;t yet handle — not a default you sprinkle everywhere &quot;to be safe.&quot;",{"id":321,"html":322,"text":322,"type":146,"level":31},"b50","FAQ",{"id":324,"html":325,"text":325,"type":146,"level":43},"b51","Does React Compiler replace useEffect?",{"id":327,"html":328,"type":135},"b52","\u003Cp>No. The compiler is entirely about memoizing render-time values and JSX; effects are how you synchronize with something outside React, and the compiler has no opinion on what belongs in one or when it should run.\u003C\u002Fp>",{"id":330,"html":331,"text":331,"type":146,"level":43},"b53","Do I still need React.memo, useMemo, or useCallback with the compiler installed?",{"id":333,"html":334,"type":135},"b54","\u003Cp>Not by default — the compiler applies equivalent memoization automatically in most cases. They remain available as an explicit escape hatch, most notably when a value is used as an effect&#39;s dependency and you need to guarantee its identity stays stable on purpose.\u003C\u002Fp>",{"id":336,"html":337,"text":337,"type":146,"level":43},"b55","Is it safe to delete all my existing useMemo\u002FuseCallback calls right now?",{"id":339,"html":340,"type":135},"b56","\u003Cp>Not automatically. React&#39;s own release guidance is to leave existing memoization in place, or remove it only after careful testing, because the compiler&#39;s generated memoization can land on slightly different boundaries than yours did.\u003C\u002Fp>",{"id":342,"html":343,"text":343,"type":146,"level":43},"b57","Does React Compiler work with React 18 or older?",{"id":345,"html":346,"type":135},"b58","\u003Cp>Yes — it supports React 17 and up. Below React 19 you add a \u003Ccode>target\u003C\u002Fcode> in the compiler config and depend on \u003Ccode>react-compiler-runtime\u003C\u002Fcode>; on React 19 that extra dependency isn&#39;t needed.\u003C\u002Fp>",{"id":348,"html":349,"text":349,"type":146,"level":43},"b59","What happens if my component breaks the Rules of React?",{"id":351,"html":352,"type":135},"b60","\u003Cp>The compiler&#39;s validation passes encode the Rules of React and surface violations as diagnostics through \u003Ccode>eslint-plugin-react-hooks\u003C\u002Fcode>. Statically detectable violations get flagged rather than silently miscompiled; violations JavaScript can&#39;t detect at compile time are the reason React recommends good test coverage before relying on the compiler in production.\u003C\u002Fp>",{"id":354,"html":355,"text":355,"type":146,"level":43},"b61","How do I stop the compiler from touching one specific component?",{"id":357,"html":358,"type":135},"b62","\u003Cp>Add the \u003Ccode>&quot;use no memo&quot;\u003C\u002Fcode> directive as the first line of that function&#39;s body.\u003C\u002Fp>",{"id":360,"html":361,"text":361,"type":146,"level":31},"b63","Cheat sheet",{"id":363,"head":364,"rows":369,"type":406},"b64",[365,366,367,368],"Task","Before (manual)","With React Compiler","Notes",[370,375,379,383,387,392,397,402],[371,372,373,374],"Skip re-rendering a child when unrelated state changes","\u003Ccode>React.memo(Child)\u003C\u002Fcode>","Automatic","Compiler keeps the child&#39;s JSX reference stable so React&#39;s reconciler bails out",[376,377,373,378],"Stabilize a callback passed to a memoized child","\u003Ccode>useCallback(fn, [deps])\u003C\u002Fcode>","Handles inline arrows written straight in JSX, which manual hooks can&#39;t",[380,381,373,382],"Avoid recomputing an expensive render-time value","\u003Ccode>useMemo(() =&gt; calc(x), [x])\u003C\u002Fcode>","Only for values computed inside a component or hook",[384,385,373,386],"Memoize a value defined after an early return","Not possible — breaks Rules of Hooks","One of the compiler&#39;s documented advantages over hooks",[388,389,390,391],"Guarantee a value&#39;s identity for an effect dependency","\u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode>","Still \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode>","Documented escape hatch — keep using it here",[393,394,395,396],"Opt one function out of compilation","—","\u003Ccode>&quot;use no memo&quot;\u003C\u002Fcode> directive","For debugging or code incompatible with the compiler",[398,399,400,401],"Enable Rules-of-React lint checks","\u003Ccode>eslint-plugin-react-compiler\u003C\u002Fcode> (superseded)","\u003Ccode>eslint-plugin-react-hooks@latest\u003C\u002Fcode>, \u003Ccode>recommended\u003C\u002Fcode> preset","Ships the compiler&#39;s lint rules directly since 1.0",[403,394,404,405],"Run on React 17\u002F18","Add \u003Ccode>react-compiler-runtime\u003C\u002Fcode> + \u003Ccode>target\u003C\u002Fcode> config","React 19 needs neither","table",{"id":408,"code":409,"type":192,"language":290,"highlight":410},"b65","# Minimal install for a React 19 project\nnpm install --save-dev --save-exact babel-plugin-react-compiler@latest\nnpm install --save-dev eslint-plugin-react-hooks@latest",[],{"id":412,"html":413,"type":135},"b66","\u003C!-- quiz:start -->",{"id":415,"html":416,"text":416,"type":146,"level":31},"b67","🧠 Test yourself",{"id":418,"html":419,"type":135},"b68","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-compiler-memoization\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":421,"html":422,"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":424,"html":425,"type":135},"b70","\u003C!-- quiz:end -->",{"id":427,"html":428,"text":428,"type":146,"level":31},"b71","Key takeaways",{"id":430,"type":152,"items":431,"ordered":18},"b72",[432,433,434,435],"React re-renders a component&#39;s whole subtree by default whenever its state changes; memoization&#39;s actual job is giving children a stable reference so React&#39;s reconciler can bail out of re-rendering them.","React Compiler 1.0, stable since October 2025, automates that memoization at build time — including cases hooks structurally cannot solve, like an inline arrow written in JSX or a value defined after an early return.","It only compiles components and hooks that hold to the Rules of React, and it only memoizes work inside them — not arbitrary functions shared across components.","\u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode> aren&#39;t obsolete. Keep them where a value&#39;s referential identity is a correctness requirement, like an effect dependency, and don&#39;t strip existing memoization from old code without testing first.",{"id":437,"html":438,"type":135},"b73","\u003Cp>The pile of \u003Ccode>useMemo\u003C\u002Fcode>\u002F\u003Ccode>useCallback\u003C\u002Fcode>\u002F\u003Ccode>memo()\u003C\u002Fcode> wrappers from the opening paragraph isn&#39;t gone because you finally found time to delete it by hand — it&#39;s gone because, as of React 19.2 with the compiler enabled, you stop needing to write most of it in the first place. The insurance policy against re-renders you were never sure would happen is now something the build step carries for you.\u003C\u002Fp>",{"id":440,"html":441,"type":135},"b74","\u003Cp>Have you turned the compiler on in a real codebase yet — did the lint rules catch anything you didn&#39;t expect? Tell me in the comments.\u003C\u002Fp>",{"id":443,"type":444},"b75","divider",{"id":446,"html":447,"type":135},"b76","\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":449,"html":450,"type":135},"b77","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":452,"type":152,"items":453,"ordered":18},"b78",[454,455,456],"⭐ \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>","Open any React codebase built before late 2025 and you'll find the same defensive scaffolding in nearly every component: a `memo()` wrapper here, a `useCallback` there, a `useMemo` around a sort you were never quite sure was expensive enough to justify it. Most of that code was never a response to a measured problem — it was insurance against a re-render you were *guessing* might happen.\n\nAs of **React Compiler 1.0**, stable since October 2025, that guessing game is mostly over. The compiler does the same analysis you were doing by hand, applies it more precisely than hooks alone can, and in a few cases does things manual memoization structurally cannot do at all. This is episode two of **React Deep Dive**, and it's about what \"automatic memoization\" actually means, what it removes from your code, and what it deliberately leaves for you to keep deciding.\n\nThis article is written against **React 19.2** (verified 19.2.8, the current npm release as of this writing) and **React Compiler 1.0** (the stable `babel-plugin-react-compiler@1.0.0` release, shipped October 7, 2025 at React Conf — verified against React's own release notes). If you want the re-render vocabulary this article leans on, [episode one covered re-render vs. remount](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Freact-re-render-vs-remount-what-actually-triggers-each-5fok) — useful background, not required reading.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Explain *why* React re-renders a whole subtree by default, and what memoization actually buys you when you add it\n- Read code with `React.memo`, `useMemo`, and `useCallback` and know exactly what problem each one was solving\n- Describe what React Compiler automates, including two cases manual hooks can't solve at all\n- Know when you still need `useMemo`\u002F`useCallback` even with the compiler installed\n- Add the compiler to a project and read its lint diagnostics when it can't safely optimize something\n\n## Who this is for\n\nYou've written function components and used `useState`, `useEffect`, and at least one of `useMemo`\u002F`useCallback`\u002F`React.memo` before, even if you couldn't fully explain why. No compiler internals or build-tooling experience required.\n\n## Table of contents\n\n- [The problem: manual memoization doesn't scale](#the-problem-manual-memoization-doesnt-scale)\n- [The mental model: what memoization actually buys you](#the-mental-model-what-memoization-actually-buys-you)\n- [Stage 1: the re-render, without memoization](#stage-1-the-re-render-without-memoization)\n- [Stage 2: the manual fix, and its subtle crack](#stage-2-the-manual-fix-and-its-subtle-crack)\n- [Stage 3: the same code, compiled](#stage-3-the-same-code-compiled)\n- [Stage 4: turning it on](#stage-4-turning-it-on)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n- [Key takeaways](#key-takeaways)\n\n## The problem: manual memoization doesn't scale\n\nHere's a dashboard with a search box and a team roster underneath it. Nothing exotic:\n\n```jsx\nfunction Dashboard({ members }) {\n  const [query, setQuery] = useState(\"\");\n\n  return (\n    \u003Cdiv>\n      \u003Cinput value={query} onChange={(e) => setQuery(e.target.value)} \u002F>\n      \u003CTeamRoster members={members} onInvite={(id) => sendInvite(id)} \u002F>\n    \u003C\u002Fdiv>\n  );\n}\n\nfunction TeamRoster({ members, onInvite }) {\n  const sorted = sortByActivity(members); \u002F\u002F recomputed on every call\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => onInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\n`members` never changes while you type. But every keystroke updates `query`, which re-renders `Dashboard`, which re-renders `TeamRoster` — by default, with no memoization applied anywhere, React re-renders a component and everything below it whenever the component's own state or a parent's state changes. `TeamRoster` re-runs `sortByActivity` from scratch, builds a brand-new array, and hands every `MemberCard` a brand-new `onInvite` closure. Every card re-renders, on every keystroke, for a value that didn't change.\n\nThe textbook fix is to memoize the boundary:\n\n```jsx\nconst TeamRoster = memo(function TeamRoster({ members, onInvite }) {\n  const sorted = useMemo(() => sortByActivity(members), [members]);\n  const handleInvite = useCallback((id) => onInvite(id), [onInvite]);\n\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => handleInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n});\n```\n\nThis helps — `sortByActivity` only re-runs when `members` actually changes. But look closely at the line that renders each card: `onInvite={() => handleInvite(m.id)}`. That inline arrow function is created fresh on every render, `useCallback` wrapper or not, so `MemberCard` still gets a new `onInvite` prop every time and still re-renders. `useCallback` cannot fix this without restructuring the code — hooks can only stabilize *values*, and the value here is the arrow function *around* `handleInvite`, not `handleInvite` itself.\n\nThis is the actual shape of the problem: correct manual memoization requires you to trace every value that flows into every child, on every edit, forever. Miss one spot — and the spot above is easy to miss — and the memoization you added silently does nothing.\n\n## The mental model: what memoization actually buys you\n\n**The mental model:** React re-renders a component whenever its own state changes *and* whenever its parent re-renders — regardless of whether the props it receives actually changed. Memoization doesn't stop a component from re-rendering because of its own state; it gives React a way to tell that a *child's* inputs didn't change, so React's reconciler can skip that child's subtree entirely rather than re-run it and diff the result.\n\nConcretely: if a component returns the exact same element reference on two consecutive renders (not just equal-looking JSX, the same object in memory), React bails out of that subtree without touching it. `React.memo` gets you this by comparing props before re-rendering the child; `useMemo`\u002F`useCallback` get you this by keeping the *values passed into* JSX stable, so the JSX built from them stays stable too.\n\nReact Compiler's entire job is producing that same stability automatically, everywhere it's provably safe to do so — by reading your component's code and figuring out, per value, whether it could have changed since the last render. It doesn't change *when* your component's own state causes it to re-render. It changes whether that re-render cascades into components that had nothing to do with the change.\n\n## Stage 1: the re-render, without memoization\n\nRun the `Dashboard`\u002F`TeamRoster` code above and every `MemberCard` logs a render on every keystroke — you can watch this happen for real in the playground below, which runs the actual React 19.2.8 runtime, not a simulation. This is React doing exactly what it's documented to do: no memoization means no bailout, so the whole subtree re-runs.\n\n**Key concept:** an unmemoized re-render isn't a bug. It's React's default, and it's usually fine — React is fast enough that most re-renders never cost anything a user would notice. The problem only shows up when a subtree is expensive enough, or large enough, that redoing it on every keystroke becomes visible.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-compiler-memoization\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Stage 2: the manual fix, and its subtle crack\n\nThe `memo`\u002F`useMemo`\u002F`useCallback` version above fixes the expensive sort but not the inline arrow, for a structural reason worth sitting with: hooks can only be called unconditionally, at the top level of a component, so you can't `useCallback` a function that's constructed *inside* a `.map()` callback per item — you'd be calling a hook in a loop, which breaks the Rules of Hooks. The only fix within hooks alone is to restructure the code, usually by pushing the click handler down into `MemberCard` and passing just the id.\n\n**Key concept:** manual memoization isn't just tedious, it has real structural gaps — situations no combination of `useMemo`\u002F`useCallback`\u002F`React.memo` can close without changing how the code is written. That's the opening React Compiler was built to close.\n\n## Stage 3: the same code, compiled\n\nWith React Compiler enabled, you write the *first* version of `TeamRoster` — no `memo`, no `useMemo`, no `useCallback`, the inline arrow left exactly where it reads most naturally:\n\n```jsx\nfunction TeamRoster({ members, onInvite }) {\n  const sorted = sortByActivity(members);\n  return (\n    \u003Cul>\n      {sorted.map((m) => (\n        \u003CMemberCard key={m.id} member={m} onInvite={() => onInvite(m.id)} \u002F>\n      ))}\n    \u003C\u002Ful>\n  );\n}\n```\n\nThe compiler analyzes the function body at build time and rewrites it — roughly, generating memoized slots for `sorted` and for each card's element, comparing them against the previous render's values, and reusing the old result when nothing relevant changed. According to React's own compiler documentation, this handles the inline-arrow case correctly *with or without* the arrow function, because the compiler is reasoning about the whole function's data flow, not applying a hook to a single named value.\n\n> This part is a **model**, not a live demo: React Compiler is a build-time Babel transform, so it cannot run inside a static HTML page without a bundler. The playground above shows the *real, measurable effect* — fewer re-renders — that flipping \"apply manual memoization\" produces with the actual React runtime; the compiled output is the same effect, generated for you, from the plain code shown here.\n\nTwo things the compiler can do that manual hooks structurally can't, both documented in its own release notes:\n\n- **Memoize after an early return.** `useMemo` and `useCallback` can't appear after a conditional `return` — that's the Rules of Hooks. The compiler isn't a hook, so it can memoize values computed after one.\n- **Handle the inline-arrow case above** without you restructuring anything.\n\n## Stage 4: turning it on\n\nInstalling it is a dev dependency plus a build-tool integration:\n\n```bash\nnpm install --save-dev --save-exact babel-plugin-react-compiler@latest\nnpm install --save-dev eslint-plugin-react-hooks@latest\n```\n\n`eslint-plugin-react-hooks`'s `recommended` and `recommended-latest` presets now ship the compiler's lint rules directly — this replaced the separate `eslint-plugin-react-compiler` package when the compiler went stable, and the lint rules work even in projects that haven't added the compiler itself yet, because they're really flagging Rules-of-React violations.\n\nNew projects scaffolded with recent versions of Vite, Next.js (15.3.1+), or Expo (SDK 54+) can start with the compiler already wired in. Existing codebases adopt it incrementally: point the compiler at one directory or route first, watch the lint output, and expand from there.\n\n## Edge cases and gotchas\n\n- **It only compiles components and hooks — not arbitrary functions.** A plain helper function called from inside a component gets memoized as a *call*, but if that same expensive helper is called from three different components, each one still pays the cost independently; the compiler's memoization isn't shared across components.\n- **It requires the Rules of React.** The compiler assumes your components and hooks are pure — idempotent given the same props\u002Fstate, no mutating props or state during render, no side effects in render. Code that breaks these rules in ways the compiler can statically detect gets flagged (surfaced through `eslint-plugin-react-hooks`, with rules like `set-state-in-render` and `set-state-in-effect`); code that breaks them in ways JavaScript can't statically catch may compile without warning and behave subtly differently than before.\n- **Removing existing manual memoization isn't automatically safe.** React's own guidance is to leave existing `useMemo`\u002F`useCallback` calls in place, or test carefully before deleting them — the compiler's memoization boundaries won't always land in exactly the same places yours did, and if some effect elsewhere depends on one of your values staying referentially stable across specific renders, changing that boundary can change how often that effect fires.\n- **React 17 and 18 are supported, not just 19** — with a `target` config option and the `react-compiler-runtime` package as an added dependency. On React 19 neither is needed.\n- **It's a build-time transform, full stop.** There's no runtime flag or devtools toggle; if it isn't wired into your bundler's config, none of this applies to your app.\n\n## Best practices\n\n- **New code: stop hand-memoizing by default.** Write the plain version. Reach for `useMemo`\u002F`useCallback` only when you need explicit control over a value's identity — most commonly, when that value is a dependency of an effect and you need to guarantee it won't cause the effect to over-fire.\n- **Turn on the compiler-powered lint rules even before you install the compiler.** They're Rules-of-React checks, and they catch real bugs (state updates during render, unsafe ref reads) independent of whether you've adopted the compiler yet.\n- **Adopt incrementally in an existing codebase.** Compile one route or directory, watch for lint diagnostics and behavior regressions, then widen the scope. Pin the compiler to an exact version (`--save-exact`) rather than a semver range if your test coverage is thin, since future versions may change memoization boundaries.\n- **Don't reach for the compiler to fix a slow function.** If `sortByActivity` above were genuinely expensive, calling it from several components would still re-run it in each one — profile first, and consider your own caching if the same expensive call is duplicated across the tree.\n- **Use `\"use no memo\"` as a scalpel, not a habit.** It opts one function out of compilation, useful while debugging a compiler diagnostic or isolating code the compiler can't yet handle — not a default you sprinkle everywhere \"to be safe.\"\n\n## FAQ\n\n### Does React Compiler replace useEffect?\n\nNo. The compiler is entirely about memoizing render-time values and JSX; effects are how you synchronize with something outside React, and the compiler has no opinion on what belongs in one or when it should run.\n\n### Do I still need React.memo, useMemo, or useCallback with the compiler installed?\n\nNot by default — the compiler applies equivalent memoization automatically in most cases. They remain available as an explicit escape hatch, most notably when a value is used as an effect's dependency and you need to guarantee its identity stays stable on purpose.\n\n### Is it safe to delete all my existing useMemo\u002FuseCallback calls right now?\n\nNot automatically. React's own release guidance is to leave existing memoization in place, or remove it only after careful testing, because the compiler's generated memoization can land on slightly different boundaries than yours did.\n\n### Does React Compiler work with React 18 or older?\n\nYes — it supports React 17 and up. Below React 19 you add a `target` in the compiler config and depend on `react-compiler-runtime`; on React 19 that extra dependency isn't needed.\n\n### What happens if my component breaks the Rules of React?\n\nThe compiler's validation passes encode the Rules of React and surface violations as diagnostics through `eslint-plugin-react-hooks`. Statically detectable violations get flagged rather than silently miscompiled; violations JavaScript can't detect at compile time are the reason React recommends good test coverage before relying on the compiler in production.\n\n### How do I stop the compiler from touching one specific component?\n\nAdd the `\"use no memo\"` directive as the first line of that function's body.\n\n## Cheat sheet\n\n| Task | Before (manual) | With React Compiler | Notes |\n| --- | --- | --- | --- |\n| Skip re-rendering a child when unrelated state changes | `React.memo(Child)` | Automatic | Compiler keeps the child's JSX reference stable so React's reconciler bails out |\n| Stabilize a callback passed to a memoized child | `useCallback(fn, [deps])` | Automatic | Handles inline arrows written straight in JSX, which manual hooks can't |\n| Avoid recomputing an expensive render-time value | `useMemo(() => calc(x), [x])` | Automatic | Only for values computed inside a component or hook |\n| Memoize a value defined after an early return | Not possible — breaks Rules of Hooks | Automatic | One of the compiler's documented advantages over hooks |\n| Guarantee a value's identity for an effect dependency | `useMemo`\u002F`useCallback` | Still `useMemo`\u002F`useCallback` | Documented escape hatch — keep using it here |\n| Opt one function out of compilation | — | `\"use no memo\"` directive | For debugging or code incompatible with the compiler |\n| Enable Rules-of-React lint checks | `eslint-plugin-react-compiler` (superseded) | `eslint-plugin-react-hooks@latest`, `recommended` preset | Ships the compiler's lint rules directly since 1.0 |\n| Run on React 17\u002F18 | — | Add `react-compiler-runtime` + `target` config | React 19 needs neither |\n\n```bash\n# Minimal install for a React 19 project\nnpm install --save-dev --save-exact babel-plugin-react-compiler@latest\nnpm install --save-dev eslint-plugin-react-hooks@latest\n```\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-compiler-memoization\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## Key takeaways\n\n- React re-renders a component's whole subtree by default whenever its state changes; memoization's actual job is giving children a stable reference so React's reconciler can bail out of re-rendering them.\n- React Compiler 1.0, stable since October 2025, automates that memoization at build time — including cases hooks structurally cannot solve, like an inline arrow written in JSX or a value defined after an early return.\n- It only compiles components and hooks that hold to the Rules of React, and it only memoizes work inside them — not arbitrary functions shared across components.\n- `useMemo`\u002F`useCallback` aren't obsolete. Keep them where a value's referential identity is a correctness requirement, like an effect dependency, and don't strip existing memoization from old code without testing first.\n\nThe pile of `useMemo`\u002F`useCallback`\u002F`memo()` wrappers from the opening paragraph isn't gone because you finally found time to delete it by hand — it's gone because, as of React 19.2 with the compiler enabled, you stop needing to write most of it in the first place. The insurance policy against re-renders you were never sure would happen is now something the build step carries for you.\n\nHave you turned the compiler on in a real codebase yet — did the lint rules catch anything you didn't expect? Tell me in the comments.\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":459,"description":109},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Freact-weekly-compiler-memoization","01a0632c-7248-73e3-9951-ef408acab2eb",{"name":462,"part":31,"total":31,"items":463},"React Deep Dive",[464,468],{"slug":465,"title":466,"publishedAt":467,"readingMinutes":111},"react-weekly-rerender-vs-remount","React Re-render vs Remount: What Actually Triggers Each","2026-08-29T12:32:41.200Z",{"slug":46,"title":108,"publishedAt":112,"readingMinutes":111},{"id":470,"locked":18},"01a0632c-7273-75ac-a11c-3a30aed8ef7f",[472],{"id":45,"slug":46,"title":48,"_count":473},{"questions":51},[475],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":477,"questionCount":51},{"questions":51}]