How to fix A component is changing an uncontrolled input to be controlled in React
This warning occurs when an input element switches from being uncontrolled (managed by the DOM) to being controlled by React state, typically when the value prop changes from undefined to a defined value. The fix involves ensuring consistent state initialization and preventing undefined values from being passed to controlled inputs.
Quick fix
Fixes A component is changing an uncontrolled input to be controlled
// ❌ Bad - starts as undefined
const [name, setName] = useState();
// ❌ Also bad - explicitly undefined
const [email, setEmail] = useState(undefined);
// ✅ Good - starts as empty string
const [name, setName] = useState('');
// ✅ Also good - explicit empty string
const [email, setEmail] = useState<string>('');- Initialize state with empty string instead of undefined
- Use nullish coalescing for potentially undefined values
- Handle async data loading with proper initialization
- Use defaultValue for truly uncontrolled inputs
// ❌ Bad - starts as undefinedconst [name, setName] = useState();// ❌ Also bad - explicitly undefinedconst [email, setEmail] = useState(undefined);// ✅ Good - starts as empty stringconst [name, setName] = useState('');// ✅ Also good - explicit empty stringconst [email, setEmail] = useState<string>('');A component is changing an uncontrolled input to be controlled
What this error means
This warning appears when React detects that an input element has switched from being uncontrolled (where the DOM manages its own state) to being controlled (where its value is managed by React state). React requires that an input element remain either controlled or uncontrolled throughout its entire lifetime. An uncontrolled input is one where you don't provide a value prop (or provide undefined/null), allowing the DOM to manage its own state. When the value prop transitions from undefined/null to a defined value (like a string), React interprets this as the input becoming controlled, which triggers this warning. This issue most commonly occurs when state is initialized as undefined, when data is being loaded asynchronously and arrives later, or when form fields conditionally receive values. React is strict about this because switching between paradigms can lead to unpredictable behavior and loss of user input.
Typical symptoms
- Console warning: "A component is changing an uncontrolled input to be controlled"
- Input field may lose user-entered text when component re-renders
- Form values don't update correctly when typing
- Component appears to work initially but breaks after data loads
- Input field behaves erratically or doesn't reflect current state
- Warning occurs specifically when async data arrives or state updates
- Multiple warnings appear if multiple form fields are affected
Common causes
- State variable initialized with undefined instead of an empty string: useState() or useState(undefined)
- Value prop receives undefined initially, then receives a string when data loads
- Conditional rendering passes undefined initially before data is available
- Object destructuring assigns undefined when expected property doesn't exist initially
- Form field mapped from array where items initially lack the required property
- Ternary operator returns undefined in initial branch: value={isLoading ? undefined : data}
- API response properties accessed before data is available
- Parent component passes undefined initially before providing actual value
How to fix "A component is changing an uncontrolled input to be controlled"
The most common fix is to ensure your state is initialized with an empty string, not undefined:
// ❌ Bad - starts as undefined
const [name, setName] = useState();
// ❌ Also bad - explicitly undefined
const [email, setEmail] = useState(undefined);
// ✅ Good - starts as empty string
const [name, setName] = useState('');
// ✅ Also good - explicit empty string
const [email, setEmail] = useState<string>('');This ensures the input is controlled from the very first render, preventing the uncontrolled-to-controlled switch.
When your value might be undefined (from props or API data), provide a fallback:
// ❌ Bad - value can be undefined initially
<input value={userData?.name} onChange={handleChange} />
// ✅ Good - always provides a string from the start
<input value={userData?.name ?? ''} onChange={handleChange} />
// Also works with logical OR
<input value={userData?.name || ''} onChange={handleChange} />The nullish coalescing operator (??) or logical OR (||) ensures the value prop never receives undefined, even during loading states.
When loading data from an API, initialize your form state before data arrives:
function UserForm() {
// ❌ Bad - starts undefined until data loads
const [name, setName] = useState(userData?.name);
// ✅ Good - starts as empty, updates when data arrives
const [name, setName] = useState('');
useEffect(() => {
if (userData?.name) {
setName(userData.name);
}
}, [userData]);
return <input value={name} onChange={(e) => setName(e.target.value)} />;
}This pattern ensures the input remains controlled even before data loads.
If you don't need to control the input with React state, make it fully uncontrolled:
// ❌ Bad - mixing uncontrolled and controlled
<input value={undefined} />
// ✅ Good - fully uncontrolled with defaultValue
<input defaultValue="" ref={inputRef} />
// Access value via ref when needed
function handleSubmit() {
const value = inputRef.current?.value;
}Use defaultValue instead of value, and access the current value via a ref. This is appropriate for simple forms where you don't need real-time validation or state synchronization.
When rendering form fields from an array, ensure each has a valid value from the start:
// ❌ Bad - some items might not have the property initially
{fields.map(field => (
<input
key={field.id}
value={field.value}
onChange={(e) => handleFieldChange(field.id, e.target.value)}
/>
))}
// ✅ Good - guarantee every input has a string value from the start
{fields.map(field => (
<input
key={field.id}
value={field.value ?? ''}
onChange={(e) => handleFieldChange(field.id, e.target.value)}
/>
))}This pattern is especially important when working with dynamic forms or field arrays that load data asynchronously.
For checkboxes and radio buttons, ensure the checked prop is always boolean:
// ❌ Bad - checked can be undefined
const [isChecked, setIsChecked] = useState();
return (
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
// ✅ Good - always boolean
const [isChecked, setIsChecked] = useState(false);
return (
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
);
// For radio buttons with potentially undefined value
<input
type="radio"
checked={selectedValue === (optionValue ?? '')}
onChange={() => setSelectedValue(optionValue ?? '')}
/>Always initialize boolean state with false (or true) rather than undefined.
Ensure conditional rendering doesn't introduce undefined values:
// ❌ Bad - renders with undefined value initially
{isLoading ? (
<Spinner />
) : (
<input value={data.name} onChange={handleChange} />
)}
// ✅ Good - always provides a value
{isLoading ? (
<input value="" onChange={handleChange} disabled />
) : (
<input value={data.name} onChange={handleChange} />
)}
// Or use a loading state with fallback
const inputValue = isLoading ? '' : data.name;
return <input value={inputValue} onChange={handleChange} />;If you need to hide inputs during loading, consider disabling them with empty values instead of removing them.
Advanced notes
TypeScript users: Enable strict null checks in your tsconfig.json to catch undefined values at compile time. Type your state explicitly as useState<string>('') rather than relying on inference.
React 19 changes: React 19 has improved handling of this scenario and provides clearer error messages. However, the fundamental rule remains: an input must be consistently controlled or uncontrolled throughout its lifetime.
Form libraries: Libraries like React Hook Form and Formik handle this complexity for you by ensuring all registered fields maintain consistent state. Consider using them for complex forms.
Select and textarea elements: This same warning applies to <select> and <textarea> elements. The fixes are identical - ensure their value props never transition from undefined to defined.
Performance consideration: If you have many form fields and are concerned about re-renders, consider using uncontrolled components with refs and accessing values only on submit. This trades off real-time validation for better performance.
Testing: When writing tests, ensure you initialize component state properly. Test utilities like React Testing Library's userEvent will respect controlled input behavior.
Common pattern with Redux: When using Redux or other state management, ensure your selectors return empty strings for missing values rather than undefined: const name = useSelector(state => state.form.name ?? '');
Key prop for truly dynamic behavior: In rare cases where you genuinely need to switch an input from uncontrolled to controlled (e.g., taking over control after user action), use a different key prop to force React to create a new instance: <input key={isControlled ? "controlled" : "uncontrolled"} ... />
Frequently asked questions
- What does "A component is changing an uncontrolled input to be controlled" mean?
- This warning appears when React detects that an input element has switched from being uncontrolled (where the DOM manages its own state) to being controlled (where its value is managed by React state). React requires that an input element remain either controlled or uncontrolled throughout its entire lifetime. An uncontrolled input is one where you don't provide a value prop (or provide undefined/null), allowing the DOM to manage its own state.
- How do I fix "A component is changing an uncontrolled input to be controlled"?
- This warning occurs when an input element switches from being uncontrolled (managed by the DOM) to being controlled by React state, typically when the value prop changes from undefined to a defined value. The fix involves ensuring consistent state initialization and preventing undefined values from being passed to controlled inputs.
- What causes "A component is changing an uncontrolled input to be controlled"?
- State variable initialized with undefined instead of an empty string: useState() or useState(undefined) Value prop receives undefined initially, then receives a string when data loads Conditional rendering passes undefined initially before data is available
- Is the "A component is changing an uncontrolled input to be controlled" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚React Official Documentation
- 📖React: Managing State(Official Docs)
- 📖React: <input> component(Official Docs)
- 📖React: Uncontrolled Components(Official Docs)