Go back

Do not trust React Compiler blindly: production experience and pitfalls ⚛️

React Compiler adoption lessons: React Hook Form, stale callbacks, the Memo badge and a safer migration checklist.

Do not trust React Compiler blindly: production experience and pitfalls ⚛️

Remember the big promises that once the stable React Compiler arrived, we could finally strip tons of useMemo and useCallback calls out of our codebases, and the compiler would perfectly divide everything into a granular cache?

It sounds great on paper, but in reality automatic optimization is a double-edged sword. Developer Isaac Okoro tested the compiler in a live Next.js production app and published a detailed breakdown of what went wrong. Spoiler: “almost everything worked,” but without good integration tests, the parts that broke would definitely have shipped and hurt users.

The main technical insights and pitfalls: 1. A frozen interactive preview (the React Hook Form problem). The form used the standard watch('name'). Without the compiler, everything updated character by character. After enabling it, the preview froze solid, even though validation and submission still worked.

  • The problem: React Hook Form and similar libraries rely on deep object mutation. The compiler, meanwhile, firmly believes your code is completely pure and predictable. When it encounters that mutation, it simply breaks reactivity. The linter reported an error immediately, and the fix required a workaround: a wrapper with the "use no memo" directive. 2. Stale data. The author completely removed a manual useCallback from a chart click handler initialized inside useEffect. Under load, clicks started referencing stale data from previous renders.
  • The problem: This is not a compiler bug; it is about the timing of useEffect itself. The manual hook pinned the reference and made effect re-registration predictable. The automation could not manage that. The callback had to be restored manually. 3. The myth of the “Memo ✨” badge in DevTools. Many people thought that if a component displayed that coveted sparkly icon in React DevTools, it was optimized. Not at all. The author deliberately fed the compiler a component that mutated props directly through .sort(). The linter complained, the code violated React’s rules, yet the “Memo” badge still proudly appeared in DevTools.
  • The takeaway: The icon only means the compiler processed the code at build time. It does not guarantee that optimization left runtime behavior intact. You need to look at the Profiler.

So, should we remove useMemo and useCallback? You can and should remove them, but selectively. Automatic caching really does work more deeply and effectively than manual caching, but the old hooks must stay if:

  • You use third-party non-React libraries (charts, canvases/Canvas, SDKs) that register callbacks strictly by reference.
  • You attach custom listeners directly to DOM APIs outside React’s lifecycle.
  • You have genuinely expensive calculations taking hundreds of milliseconds (here, useMemo is needed at least as documentation for colleagues: “don’t touch this, it’s expensive”).

Migration checklist: First install the latest eslint-plugin-react-hooks and set the unsupported-syntax and incompatible-library rules to error. Clean up whatever the linter flags in the codebase. Only then enable the compiler experimentally, making sure to pin the plugin version with --save-exact so that a minor update does not throw off the build logic.

Read the breakdown

How is React Compiler going for you? Have you tried bringing it into large projects, or are you still sticking with manually managed hook dependencies because “it’s more reliable”? 👇

Copied