When I first started building forms in my current project, I ran into this error:
hook.js:608 A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
It appeared in my browser console like an angry red flag every time my form loaded.
At first, I didn’t fully understand why React was upset. But after digging in, I realized the issue was this:
The root cause
An uncontrolled input becomes controlled if its value prop changes from undefined to a defined value after the first render.
Example of the problem:
<input value={formData.name} onChange={handleChange} />
If formData.name is undefined at first (maybe the data is still loading), React treats the input as uncontrolled.
But when the data finally loads and formData.name becomes "User", React sees that as switching from uncontrolled → controlled.
How I fixed it
I ensured the value was always defined, even before data loaded:
<input
value={formData.name ?? ""}
onChange={handleChange}
/>
Or, when using React Hook Form, I provided defaultValue explicitly:
<input {...register("name")} defaultValue="" />
For controlled components wrapped with Controller:
<Controller
name="username"
control={control}
defaultValue="" // important!
render={({ field }) => <InputText {...field} />}
/>
Lesson learned
- Always decide upfront if your input will be controlled or uncontrolled.
- If controlled, initialize value to an empty string (or a sensible default) instead of letting it be undefined.
- If uncontrolled, don’t bind a value prop at all — use defaultValue or refs.
This small detail saved me hours of debugging and also made me appreciate why libraries like React Hook Form handle defaults so gracefully.
Top comments (0)