{"id":66,"date":"2026-04-14T07:07:37","date_gmt":"2026-04-14T07:07:37","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/?p=66"},"modified":"2026-04-14T07:07:37","modified_gmt":"2026-04-14T07:07:37","slug":"react-compiler-what-it-does-to-your-code","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/react-compiler-what-it-does-to-your-code\/","title":{"rendered":"React Compiler: what it actually does to your code"},"content":{"rendered":"<p>I mass-deleted 31 useMemo calls from a production codebase last month. Not because I got reckless, but because the React Compiler made every single one of them redundant. And honestly? The diff looked beautiful.<\/p>\n<p>If you&rsquo;ve been writing React for more than a year, you&rsquo;ve been trained to think about memoization constantly. Wrap this in useCallback. Wrap that in useMemo. Slap React.memo on the component that re-renders too much. It works, but it&rsquo;s annoying, error-prone, and adds visual clutter to code that should be simple. The React Compiler changes that, and it does it in a way that&rsquo;s worth understanding, not just adopting blindly.<\/p>\n<h2 id=\"what-the-react-compiler-is-and-isnt\">What the React Compiler is (and isn&rsquo;t)<\/h2>\n<p>The React Compiler is an ahead-of-time build tool that analyzes your components and automatically inserts memoization where it helps. It shipped as part of the <a href=\"https:\/\/react.dev\/blog\/2024\/12\/05\/react-19\" rel=\"nofollow noopener\" target=\"_blank\">React 19 release<\/a> and currently works as a Babel plugin (or SWC plugin if you&rsquo;re on Next.js 15+).<\/p>\n<p>It is not a runtime thing. It doesn&rsquo;t watch your components and decide at render time what to cache. It reads your source code at build time, figures out which values and functions are stable across renders, and rewrites the output to memoize them automatically. Think of it like a really opinionated linter that also fixes the problems it finds.<\/p>\n<p>What it isn&rsquo;t: magic. It can&rsquo;t fix components that violate the <a href=\"https:\/\/react.dev\/reference\/rules\" rel=\"nofollow noopener\" target=\"_blank\">rules of React<\/a>. If you&rsquo;re mutating props, reading from mutable globals, or doing side effects during render, the compiler will either skip your component or bail out. It has a strict mode that warns you about these things, which is actually one of the best parts: it forces you to write cleaner React whether you use the compiler output or not.<\/p>\n<h2 id=\"the-memoization-tax-youve-been-paying\">The memoization tax you&rsquo;ve been paying<\/h2>\n<p><img decoding=\"async\" alt=\"React Compiler: what it actually does to your code\" src=\"https:\/\/abrarqasim.com\/blog\/wp-content\/uploads\/2026\/04\/react-compiler-what-it-does-to-your-code-inline-1776142872.png\"><\/p>\n<p>Here&rsquo;s a component I had in production. It&rsquo;s a product card that receives a price object and a callback for adding to cart:<\/p>\n<pre><code class=\"language-jsx\">\/\/ Before: React 18 with manual memoization\nconst ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {\n  const formattedPrice = useMemo(\n    () =&gt; formatCurrency(product.price, product.currency),\n    [product.price, product.currency]\n  );\n\n  const handleClick = useCallback(() =&gt; {\n    onAddToCart(product.id, 1);\n  }, [onAddToCart, product.id]);\n\n  const tagList = useMemo(\n    () =&gt; product.tags.filter(t =&gt; t.featured).map(t =&gt; t.label),\n    [product.tags]\n  );\n\n  return (\n    &lt;div className=&quot;card&quot;&gt;\n      &lt;h3&gt;{product.name}&lt;\/h3&gt;\n      &lt;span&gt;{formattedPrice}&lt;\/span&gt;\n      &lt;TagList tags={tagList} \/&gt;\n      &lt;button onClick={handleClick}&gt;Add to cart&lt;\/button&gt;\n    &lt;\/div&gt;\n  );\n});\n<\/code><\/pre>\n<p>Three hooks and a wrapper, all doing the same job: &ldquo;please don&rsquo;t recompute this unless the inputs change.&rdquo; I had to manually track every dependency array and keep them in sync. Miss one, and you get stale data. Add an extra, and the memoization is pointless.<\/p>\n<p>Here&rsquo;s the same component written for the compiler:<\/p>\n<pre><code class=\"language-jsx\">\/\/ After: React 19 with the compiler\nfunction ProductCard({ product, onAddToCart }) {\n  const formattedPrice = formatCurrency(product.price, product.currency);\n\n  const handleClick = () =&gt; {\n    onAddToCart(product.id, 1);\n  };\n\n  const tagList = product.tags.filter(t =&gt; t.featured).map(t =&gt; t.label);\n\n  return (\n    &lt;div className=&quot;card&quot;&gt;\n      &lt;h3&gt;{product.name}&lt;\/h3&gt;\n      &lt;span&gt;{formattedPrice}&lt;\/span&gt;\n      &lt;TagList tags={tagList} \/&gt;\n      &lt;button onClick={handleClick}&gt;Add to cart&lt;\/button&gt;\n    &lt;\/div&gt;\n  );\n}\n<\/code><\/pre>\n<p>Same behavior. Same performance. Less code. The compiler sees that <code>formattedPrice<\/code> only depends on <code>product.price<\/code> and <code>product.currency<\/code>, so it memoizes the computation. It sees <code>handleClick<\/code> closes over <code>onAddToCart<\/code> and <code>product.id<\/code>, so it caches the function reference. It sees the filtered <code>tagList<\/code> depends on <code>product.tags<\/code>, so it memoizes that too. All without me writing a single dependency array.<\/p>\n<h2 id=\"how-to-turn-it-on\">How to turn it on<\/h2>\n<p>If you&rsquo;re on Next.js 15+, it&rsquo;s one line in <code>next.config.js<\/code>:<\/p>\n<pre><code class=\"language-js\">\/\/ next.config.js\nmodule.exports = {\n  experimental: {\n    reactCompiler: true,\n  },\n};\n<\/code><\/pre>\n<p>For Vite or a custom Webpack setup, you add the Babel plugin:<\/p>\n<pre><code class=\"language-bash\">npm install -D babel-plugin-react-compiler\n<\/code><\/pre>\n<pre><code class=\"language-js\">\/\/ babel.config.js\nmodule.exports = {\n  plugins: [\n    ['babel-plugin-react-compiler', {}],\n  ],\n};\n<\/code><\/pre>\n<p>The compiler runs on every component by default. If it can&rsquo;t safely optimize a component (because it detects a rules violation), it skips that component and moves on. Your app still works. You can also opt in gradually by adding <code>'use memo'<\/code> directives to specific files, though I haven&rsquo;t found that necessary in practice.<\/p>\n<p>One thing that tripped me up: the compiler needs React 19. If you&rsquo;re still on 18, you need to upgrade first. The <a href=\"https:\/\/react.dev\/blog\/2024\/04\/25\/react-19-upgrade-guide\" rel=\"nofollow noopener\" target=\"_blank\">React 19 upgrade guide<\/a> covers the breaking changes, and most of them are minor. The biggest one for me was the ref callback cleanup function, which took about 20 minutes to fix across the whole app.<\/p>\n<h2 id=\"where-the-compiler-actually-helps-and-where-it-doesnt\">Where the compiler actually helps (and where it doesn&rsquo;t)<\/h2>\n<p>I ran the compiler on three projects over the past couple months. Here&rsquo;s what I found.<\/p>\n<p>It helps a lot when you have deeply nested component trees where parent re-renders cascade down. The kind of app where you&rsquo;d normally reach for React.memo on five or six child components. The compiler handles all of that, and it&rsquo;s smarter about it than most developers are. It can memoize individual JSX subtrees within a component, not just the whole thing.<\/p>\n<p>It also helps when you have derived state. If you&rsquo;re computing something from props and using it in the render, the compiler memoizes that computation with the right granularity. No more second-guessing whether your useMemo dependency array is correct.<\/p>\n<p>Where it doesn&rsquo;t help: components with genuine side effects during render, components that rely on mutable refs for render logic, and components that read from external stores without useSyncExternalStore. The compiler can&rsquo;t optimize what it can&rsquo;t prove is pure. It also won&rsquo;t help with expensive initial renders, because the cost is in the first computation, not the re-computation.<\/p>\n<p>I measured render times on a dashboard page with about 40 components. Before the compiler, with manual memoization in place, a filter change took about 180ms of React work. After switching to the compiler (and removing all the manual memos), the same interaction took about 165ms. Not dramatic, but the code is significantly easier to read now. That&rsquo;s the real win.<\/p>\n<h2 id=\"the-eslint-plugin-you-should-install-first\">The eslint plugin you should install first<\/h2>\n<p>Before you turn on the compiler, install <code>eslint-plugin-react-compiler<\/code>. It flags components that violate the rules of React and would cause the compiler to bail out. I ran it on that production codebase and found 4 components with issues: two were mutating objects during render, one was reading from a module-level variable that changed, and one had a conditional hook call hidden behind an early return.<\/p>\n<p>Fixing those four components took about an hour. After that, the compiler optimized every component in the project without a single bail-out.<\/p>\n<p>This is actually my favorite side effect of the compiler existing. Even if you decide not to use it, the eslint plugin pushes you toward writing React components that are easier to reason about. Pure functions with predictable outputs from their inputs. That&rsquo;s been the React mental model since day one, but before the compiler there was no tool that actually enforced it.<\/p>\n<h2 id=\"should-you-adopt-it-now\">Should you adopt it now<\/h2>\n<p>If you&rsquo;re starting a new project on React 19, yes, turn it on. There&rsquo;s no downside. You write simpler code and the compiler handles the optimization.<\/p>\n<p>If you have an existing project, the migration path is low-risk but not zero-effort. Upgrade to React 19, run the eslint plugin, fix the violations, enable the compiler, remove your manual useMemo\/useCallback\/React.memo calls. I did this on a ~200 component project and it took a weekend. Most of the time was the React 19 upgrade itself, not the compiler.<\/p>\n<p>If you&rsquo;re on React Native, compiler support is still experimental as of early 2026. It works in some setups but the team hasn&rsquo;t officially blessed it for production RN apps yet.<\/p>\n<p>The practical takeaway: the React Compiler is the best thing to happen to React DX in years. It doesn&rsquo;t change what React is. It removes the tedious parts of writing it correctly. If you&rsquo;ve been putting off the React 19 upgrade, this is the reason to stop procrastinating. I wrote about <a href=\"https:\/\/abrarqasim.com\/blog\/open-source-llms-2026\" rel=\"noopener\">how I approach evaluating new tools<\/a> on this blog before, and the compiler passed every test I care about: it solves a real problem, it fails gracefully, and the code it produces is something I&rsquo;d write by hand if I had infinite patience.<\/p>\n<p>You can check out more of my work and the projects where I applied this at <a href=\"https:\/\/abrarqasim.com\" rel=\"noopener\">abrarqasim.com<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The React Compiler removes manual useMemo and useCallback from your components. Here&#8217;s what changed, with real before-and-after code examples.<\/p>\n","protected":false},"author":2,"featured_media":64,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"The React Compiler removes manual useMemo and useCallback from your components. Here's what changed, with real before-and-after code examples.","rank_math_focus_keyword":"react compiler","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[35],"tags":[38,44,19,41,43,42,39],"class_list":["post-66","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-web-development","tag-frontend","tag-javascript","tag-performance","tag-react","tag-react-19","tag-react-compiler","tag-web-development"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/66","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=66"}],"version-history":[{"count":1,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/66\/revisions"}],"predecessor-version":[{"id":67,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/66\/revisions\/67"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/64"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=66"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=66"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=66"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}