The Invisible Form Bug: React 19 + React Hook Form's Hidden Compatibility Issue
That toggle that worked perfectly in React 18 now does nothing when clicked? You're not alone—and the fix isn't in any migration guide.

If you've recently upgraded to React 19 and found that your React Hook Form components aren't re-rendering when form values change, you're not alone. This is a common issue that has caught many developers by surprise, especially when using the watch
API.
The Problem
You have a form built with React Hook Form, and you're using the watch
method to track changes to form values. Your code might look something like this:
function MyFormComponent() {
const methods = useForm({
defaultValues: initialData
});
// Using watch to observe form values
const someFormValue = methods.watch("someField");
// Render UI based on watched value
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{/* Form fields */}
{/* Conditional rendering based on watched value */}
{someFormValue && <SomeConditionalComponent />}
</form>
</FormProvider>
);
}
But in React 19, you've noticed that when someFormValue
changes, your component doesn't re-render to show or hide SomeConditionalComponent
. This is frustrating because:
- Your form values are actually updating correctly (you can verify this by logging or by forcing a re-render)
- This same code worked perfectly in React 18 or earlier
The Cause
With React 19's changes to the rendering model, React Hook Form's watch
method no longer triggers component re-renders as reliably as before. This is because:
- React 19 implements more aggressive batching of updates
- React Hook Form's
watch
is optimized for access to form values but not necessarily for triggering re-renders
The Solution: useWatch
The solution is simple but not immediately obvious: use useWatch
instead of watch
for values that need to trigger UI updates.
According to the React Hook Form documentation:
[
useWatch
] behaves similarly to the watch API, however, this will isolate re-rendering at the custom hook level and potentially result in better performance for your application.
Here's how to refactor your code:
import { useForm, useWatch, FormProvider } from "react-hook-form";
function MyFormComponent() {
const methods = useForm({
defaultValues: initialData
});
// Using useWatch instead of watch
const someFormValue = useWatch({
control: methods.control,
name: "someField",
defaultValue: initialData.someField // Optional, but recommended
});
// Now your component will re-render when someFormValue changes
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{/* Form fields */}
{/* This will now work as expected */}
{someFormValue && <SomeConditionalComponent />}
</form>
</FormProvider>
);
}
Example 1: Simple Toggle
Let's say you have a form with a checkbox that toggles the visibility of additional fields:
import React from "react";
import { useForm, useWatch, FormProvider } from "react-hook-form";
function ToggleFieldsForm() {
const methods = useForm({
defaultValues: {
enableAdditionalFields: false,
additionalField1: "",
additionalField2: ""
}
});
// This will trigger re-renders when the value changes
const showAdditionalFields = useWatch({
control: methods.control,
name: "enableAdditionalFields",
defaultValue: false
});
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(data => console.log(data))}>
<div>
<label>
<input
type="checkbox"
{...methods.register("enableAdditionalFields")}
/>
Show additional fields
</label>
</div>
{showAdditionalFields && (
<div className="additional-fields">
<div>
<label>Additional Field 1</label>
<input {...methods.register("additionalField1")} />
</div>
<div>
<label>Additional Field 2</label>
<input {...methods.register("additionalField2")} />
</div>
</div>
)}
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
Example 2: Complex Conditional Logic
For more complex scenarios, like an opening hours form where different sections appear based on multiple conditions:
import React from "react";
import { useForm, useWatch, FormProvider, Controller } from "react-hook-form";
function OpeningHoursForm() {
const methods = useForm({
defaultValues: {
useCustomHours: false,
regularHours: {
monday: { open: "9:00", close: "17:00" },
tuesday: { open: "9:00", close: "17:00" },
// other days...
},
useEmergencyHours: false,
emergencyHours: {
monday: { open: "8:00", close: "20:00" },
// other days...
}
}
});
// Use useWatch for values that affect rendering
const useCustomHours = useWatch({
control: methods.control,
name: "useCustomHours",
defaultValue: false
});
const useEmergencyHours = useWatch({
control: methods.control,
name: "useEmergencyHours",
defaultValue: false
});
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(data => console.log(data))}>
<div>
<Controller
name="useCustomHours"
control={methods.control}
render={({ field }) => (
<label>
<input
type="checkbox"
checked={field.value}
onChange={e => field.onChange(e.target.checked)}
/>
Use custom hours message
</label>
)}
/>
</div>
{!useCustomHours ? (
<>
{/* Regular hours section */}
<div className="regular-hours">
<h3>Regular Hours</h3>
{/* Regular hours fields */}
</div>
<div>
<Controller
name="useEmergencyHours"
control={methods.control}
render={({ field }) => (
<label>
<input
type="checkbox"
checked={field.value}
onChange={e => field.onChange(e.target.checked)}
/>
Enable emergency hours
</label>
)}
/>
</div>
{useEmergencyHours && (
<div className="emergency-hours">
<h3>Emergency Hours</h3>
{/* Emergency hours fields */}
</div>
)}
</>
) : (
<div className="custom-message">
<h3>Custom Hours Message</h3>
<textarea {...methods.register("customMessage")} />
</div>
)}
<button type="submit">Save Hours</button>
</form>
</FormProvider>
);
}
Key Takeaways
- In React 19, use
useWatch
instead ofwatch
for values that need to trigger UI updates - Always provide a
defaultValue
touseWatch
to ensure consistency during the initial render - For optimal performance, only use
useWatch
for values that directly affect rendering; continue usingwatch
for other cases
By following these guidelines, your React Hook Form components will behave as expected in React 19, with reliable re-rendering when form values change.
Remember, this change is particularly important if you're:
- Using conditional rendering based on form values
- Showing/hiding form sections based on toggles
- Implementing dynamic form logic that depends on the current state of form values
Happy form building!