Changing an uncontrolled input to be controlled
A form field started with no value and then got one, so React switched from letting the browser manage it to managing it from state.
Also appears as
- A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://react.dev/link/controlled-components
- `value` prop on `input` should not be null. Consider using an empty string to clear the component or `undefined` for uncontrolled components.
- Warning: 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. More info: https://reactjs.org/link/controlled-components
Prompt to fix it
React warns "A component is changing an uncontrolled input to be controlled." Find the input in the component stack and check what its value or checked prop is on the first render. Make it controlled from the start: initialize state with an empty string or false, give form objects every field up front, and fall back with ?? "" for nullable database fields. If the form edits existing data, render it after the data loads and reset it with a key when the record changes. Keep each input in one mode for its whole life.
Paste into Layerwise or any AI coding tool.
What it means
React form fields work in one of two modes. A controlled input gets its text from your state through value and reports every keystroke through onChange. An uncontrolled input keeps its own text in the browser, and you read it when you need it. React expects each field to pick one mode and keep it.
React treats value={undefined} and value={null} as “uncontrolled.” So when state starts as undefined and later becomes a string, for example after data loads, the field switches modes and React warns. Anything the user typed before the switch can be overwritten.
Database rows are the usual source. A nullable column comes back as null, and a missing field reads as undefined. Fall back to an empty string at the input, as in value={profile.bio ?? ''}, and the field stays controlled the whole time.
Common causes
- State starts empty, as in
useState()oruseState<string>(), and gets a string once the user types or data loads. - A form object fills in over time, so
form.emailisundefineduntil the user edits it. - A nullable database column arrives as
nulland goes straight intovalue. - A checkbox uses
checked={user?.isAdmin}whileuseris still loading.
How to fix it
- Find the input in the warning’s component stack and check what its
valueorcheckedis on the first render. - Start text state with an empty string,
useState(''), and give form objects every field up front. - Fall back at the input for data that can be missing:
value={todo.title ?? ''}andchecked={todo.done ?? false}. - If you don’t need each keystroke in state, make the field uncontrolled with
defaultValueand read it from the form on submit. - For an edit form, render it once the data arrives and give it
key={record.id}so it starts fresh for each record.
Example fix
import { useState } from 'react';
type Profile = { id: string; name: string | null; newsletter: boolean | null };
// Render as <ProfileForm key={profile.id} profile={profile} /> once data loads.
export function ProfileForm({ profile }: { profile: Profile }) {
// Strings and booleans from the start keep both inputs controlled.
const [name, setName] = useState(profile.name ?? '');
const [newsletter, setNewsletter] = useState(profile.newsletter ?? false);
return (
<form>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input
type="checkbox"
checked={newsletter}
onChange={(e) => setNewsletter(e.target.checked)}
/>
</form>
);
}Related errors
All errorsCannot read properties of undefined
The code asked for a property of a value that doesn’t exist yet, usually data that hasn’t loaded or a field the API didn’t return.
Objects are not valid as a React child
React was asked to show a whole object, such as a database row or a date, on the page, and it can only show text, numbers, and elements.
Hydration failed
The HTML the server sent didn’t match what React rendered first in the browser, so React threw that part away and rebuilt it on the client.
Too many re-renders
A component changes its own state while it renders, so React renders it again and again until it stops the loop.
Build with an agent that checks its work
The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.