Skip to content
Malik Hamza Shabbir
Reactreact compilerreact hook formuse no memonext.js

React Compiler 1.0 Broke My Forms: Fixing React Hook Form with 'use no memo' (and When to Wait for RHF v8)

HSMalik Hamza ShabbirUpdated 8 min read

In short

React Compiler 1.0 memoizes components that React Hook Form expects to re-render on ref mutations, so fields silently stop updating. The fix is the 'use no memo' directive scoped as tightly as possible (ideally a tiny useFormCompat wrapper hook), plus the react-hooks/incompatible-library lint to catch every offending component. Patch today, watch my web development work approach, and only wait for RHF v8 if you can hold a few months.

React Compiler 1.0 Broke My Forms: Fixing React Hook Form with 'use no memo' (and When to Wait for RHF v8)
On this page

If you just enabled React Compiler 1.0 and your React Hook Form fields stopped updating, the fix is the 'use no memo' directive scoped as tightly as you can manage, ideally inside a small wrapper hook around useForm. RHF tracks inputs through mutable refs and proxy reads instead of useState setters, and the compiler's auto-memoization assumes a component only re-renders when its tracked reactive values change. Those two models disagree, so the compiler skips renders RHF was counting on. You do not have to abandon either tool. You scope the opt-out, lint for every offender, and decide separately whether v8 is worth waiting for.

I hit this on a client form-heavy admin the week after I turned the compiler on. Everything looked fine until someone typed into a field and nothing downstream reacted. Here is exactly what is happening and how I fix it without turning the compiler off for the whole app.

Why does React Compiler break React Hook Form?

React Compiler breaks React Hook Form because RHF updates inputs through mutated refs and a read-tracking proxy, while the compiler assumes a component re-renders only when its reactive inputs change. The compiler memoizes the render output, so the mutation-driven updates RHF expects never reach the screen.

Most React state is honest with the compiler. You call a useState setter, React schedules a render, and the compiler can see the dependency graph and decide what to recompute. RHF deliberately does not work that way. To keep large forms fast, it registers inputs as uncontrolled, mutates a fieldsRef object directly, and only triggers a render when something actually subscribed to that field. The formState object is a proxy: reading formState.errors is what subscribes you to error changes. If you never read it, RHF never re-renders for it. That is the whole performance story behind the library.

React Compiler 1.0, released 7 October 2025, looks at a component, builds a dependency model from the values it can see, and wraps computations in memoization so they only rerun when those values change. It has no idea that a ref mutation three layers down was supposed to be load-bearing, because a mutated ref is invisible to the reactive model by design. So the compiler concludes a re-render is unnecessary and caches the previous output. The form looks frozen.

The symptoms are specific and worth recognizing:

  • You type, but watch() values or dependent fields do not update.

  • Validation errors do not appear or do not clear after you fix the input.

  • isDirty, isValid, or formState flags lag behind the real input state.

  • useFieldArray rows render stale data after add or remove.


None of this throws. That is what makes it nasty. The build is green, the types pass, and the bug only shows up when a human interacts with the form.

What does the 'use no memo' directive do?

'use no memo' is a function-level directive that tells React Compiler to skip optimizing the function it appears in, leaving the manual render behavior intact. It is the official, supported escape hatch for code that the compiler cannot safely transform, and React Hook Form is the most common reason to reach for it.

You place the string literal as the first statement inside the function, the same way 'use client' sits at the top of a file:

TSX
function CheckoutForm() {
  'use no memo';

  const { register, handleSubmit, watch, formState } = useForm<Checkout>();
  // ...the rest behaves exactly as it did before the compiler
}

The compiler reads that directive, leaves the function untransformed, and your form goes back to working. This is not a hack the community invented. It is part of the compiler's design for exactly these cases where the library reaches outside the reactive model.

The trade is that this function no longer gets the compiler's automatic memoization. For a form component that is usually fine, because RHF already minimizes renders on its own. But the directive is contagious in scope: put it on a 400-line page component and you have opted that entire tree's render path out of optimization. So the real skill here is not knowing the directive exists, it is scoping it down.

How do I scope 'use no memo' to the smallest possible surface?

Scope it by wrapping useForm in a tiny custom hook that carries the directive, then use that hook everywhere instead of useForm directly. The opt-out then lives in one ten-line function, and every component that consumes the form stays fully compiled.

This is the pattern I standardize on across a codebase. Call it useFormCompat:

TSX
// hooks/use-form-compat.ts
import { useForm, type UseFormProps, type FieldValues } from 'react-hook-form';

export function useFormCompat<T extends FieldValues>(props?: UseFormProps<T>) {
  'use no memo';
  return useForm<T>(props);
}

TSX
// CheckoutForm.tsx  -- this component still gets compiled
import { useFormCompat } from '@/hooks/use-form-compat';

function CheckoutForm() {
  const { register, handleSubmit, formState } = useFormCompat<Checkout>();
  // compiler optimizes this component normally
}

The directive sits inside useFormCompat, so only that hook's body is excluded from compilation. The useForm call and its mutable internals run un-memoized, which is what RHF needs, while your actual form component, including all the JSX and event handlers, gets the compiler's optimizations.

One caveat I learned the hard way: the directive opts out the function it is written in, not the component that calls the hook. In the early days of compiler adoption there was confusion about whether putting 'use no memo' in a hook would protect the calling component too. It does not, and you do not want it to. The component is fine. It is the form's internal bookkeeping that needs to be left alone, and that bookkeeping lives inside useForm. Wrapping useForm is the correct boundary.

If you use useFieldArray or useFormContext, wrap those the same way and route the whole app through the wrappers. A barrel file makes the migration a find-and-replace:

TSX
// hooks/rhf.ts
export { useFormCompat as useForm } from './use-form-compat';
export { useFieldArrayCompat as useFieldArray } from './use-field-array-compat';

Then your import sites change from from 'react-hook-form' to from '@/hooks/rhf' and nothing else moves.

Can eslint find every form that needs the fix?

Yes. eslint-plugin-react-hooks v7.1.0, released April 2026, ships a react-hooks/incompatible-library rule that flags components using libraries the compiler cannot safely optimize, including React Hook Form, and it can autofix the opt-out in for you.

This rule matters because the bug is silent. Without lint, the only way to find every broken form is to click through the entire app, and you will miss one. The rule reads your code statically and tells you which components touch RHF internals in a way the compiler will mishandle.

Enable it in your flat config:

JS
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  {
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/incompatible-library': 'warn',
    },
  },
];

Then run the autofix once across the repo:

BASH
npx eslint . --fix

The autofix inserts the 'use no memo' directive where it is needed. I still review the diff, because the autofix puts the directive on the component that uses RHF rather than refactoring you into a useFormCompat wrapper. For a quick unblock, take the autofix. For a codebase you will maintain for years, I run the autofix to find the offenders, then replace each inline directive with the wrapper hook so the blast radius stays small. The lint is the detector; the wrapper is the cure.

Keep the rule at 'warn' rather than 'error' while you migrate, so a missed form does not break CI mid-rollout, then promote it to 'error' once the codebase is clean.

Should I patch with 'use no memo' now or wait for React Hook Form v8?

Patch now with the scoped opt-out unless you can comfortably delay shipping for a few months, because RHF v8 is still in beta as of mid-2026 and you should not block production on a beta dependency. The 'use no memo' wrapper is stable, reversible, and removable later in one place.

RHF v8 is being rebuilt so its internals cooperate with the compiler instead of fighting it, which means the eventual upgrade should let you delete the wrapper entirely. But beta means the API can still move, and putting a beta form library under a checkout or onboarding flow is a risk I would not take for a client. Here is the decision I actually use:







SituationWhat I do
Shipping to production this quarterScoped 'use no memo' via useFormCompat, lint at 'warn'
Greenfield app, compiler on from day oneSame wrapper pattern, baked into the import barrel from the start
Internal tool, low traffic, can absorb churnTry RHF v8 beta, keep the wrapper as a fallback
Large form-heavy app, long maintenance horizonPatch today, schedule the v8 migration when it leaves beta
Can delay the form work by a few months anywayWait for the v8 stable release

The thing I want to be clear about: the wrapper and v8 are not competing strategies, they are sequential. You patch today so you can ship, and the wrapper localizes the eventual removal to a single file. When v8 goes stable, you swap useForm back in, delete useFormCompat, and the lint rule confirms there is nothing left to opt out. That is the cheapest possible path through this, and it is the same staged approach I take with the rest of the React 19 and Next.js churn, like the Next.js 16 'use cache' migration and the middleware.ts to proxy.ts move . Adopt the new thing, scope the compatibility seams, and keep removal cheap.

If you are also still working through the React Server Components security work from earlier in the year, the CVE-2025-55182 upgrade map pairs naturally with this, because both are about upgrading the React stack without a regression slipping into production.

A quick checklist before you turn the compiler back on

Here is the order I run when adopting React Compiler on a form-heavy app:

  1. Add eslint-plugin-react-hooks v7.1.0 and enable react-hooks/incompatible-library at 'warn'.

  2. Run npx eslint . --fix to surface every RHF component the compiler will mishandle.

  3. Create useFormCompat (and field-array and context equivalents) carrying 'use no memo'.

  4. Route imports through a barrel so every form uses the wrappers.

  5. Click through your real forms: type, validate, submit, add and remove field-array rows.

  6. Promote the lint rule to 'error' once clean.

  7. Note the v8 migration as a follow-up for when it leaves beta.


That sequence is what turns a silent, scary breakage into a contained, twenty-minute change. The compiler stays on and keeps optimizing everything that is not a form, your forms work, and you have one file to delete when RHF v8 finally lands.

If a compiler upgrade quietly broke your app, that is the kind of fix I handle through web development .

If you are mid-migration and a form is misbehaving in a way that does not match what I described here, or you want a second set of eyes on a compiler rollout before it ships, my contact page is the fastest way to reach me. I do this kind of upgrade work often enough that the weird cases tend to look familiar.

FAQ

Why does React Compiler break React Hook Form?

React Hook Form tracks input state through mutable refs and proxy reads instead of state setters, so the compiler memoizes components that RHF expects to re-render and the UI stops reflecting form changes.

What does the 'use no memo' directive do?

The `'use no memo'` directive tells React Compiler to skip optimizing the specific function it sits in, restoring the manual render behavior that React Hook Form relies on.

Where should I put 'use no memo' for the smallest blast radius?

Put it inside a tiny wrapper hook like `useFormCompat` that calls `useForm`, so only that hook opts out while the rest of your component still gets compiled.

Can eslint find every component that needs the opt-out?

Yes, eslint-plugin-react-hooks v7.1.0 ships a `react-hooks/incompatible-library` rule that flags RHF usage the compiler cannot safely optimize, and it can autofix the directive in.

Should I wait for React Hook Form v8 instead of patching?

Wait only if you can delay a few months, since RHF v8 is still beta in mid-2026; if you are shipping now, the scoped `'use no memo'` opt-out is the safe choice.

Working on something like this?

I build web apps, AI features, and mobile products for clients. If this article matches a problem you have, tell me about it.

Start a conversation
HS

Malik Hamza Shabbir · Full-Stack & AI Engineer

I build full-stack and AI products solo: a reputation SaaS in production, RAG pipelines, and React Native apps. I write from what I ship, not from documentation summaries.

Related articles