"A Component Is Changing an Uncontrolled Input to Be Controlled": Fix
React warns when an input's value starts as undefined and later becomes a string. What controlled and uncontrolled inputs are, why undefined initial state causes this, and the fixes: initialise with '', use ?? '' for loaded data, and handle checkboxes and selects properly.
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.
The warning tells you the cause directly: an input's value started as undefined and later became a real value. To see why that matters, you need two terms.
Controlled vs uncontrolled inputs
Controlled: React owns the value. You pass value and update it with onChange.
const [email, setEmail] = useState('')
<input value={email} onChange={e => setEmail(e.target.value)} />
Uncontrolled: the browser owns the value. You don't pass value (maybe defaultValue), and read it when needed — for example from a form submission or a ref.
<input name="email" defaultValue="" />
Both are fine. The problem is switching between them. If value={undefined}, React treats the input as uncontrolled. When it later becomes value="ada@example.com", it's suddenly controlled. React warns because it can't manage that switch cleanly.
Cause 1: state initialised without a value
const [name, setName] = useState() // ❌ undefined
<input value={name} onChange={e => setName(e.target.value)} />
Fix: start with an empty string.
const [name, setName] = useState('') // ✅
Cause 2: a form object with missing fields
const [form, setForm] = useState({})
<input value={form.email} ... /> // form.email is undefined at first
Fix: initialise every field.
const [form, setForm] = useState({ email: '', name: '' })
Cause 3: data loaded from an API
The form shows before the data arrives, or a field from the database is null:
<input value={user?.phone} ... /> // undefined, then a string (or null)
Fix: fall back to an empty string.
<input value={user?.phone ?? ''} ... />
Note that null causes a similar warning ("value prop on input should not be null"). ?? '' handles both. (NULL in SQL)
Alternatively, don't render the form until the data has loaded.
Checkboxes and selects
Checkboxes use checked, not value, and need a boolean:
const [agree, setAgree] = useState(false) // not undefined
<input type="checkbox" checked={agree} onChange={e => setAgree(e.target.checked)} />
For <select>, initialise with one of the option values (or '' with a placeholder option).
The reverse warning
"A component is changing a controlled input to be uncontrolled" is the same problem the other way: the value went from a string to undefined — often because a state update replaced the whole form object and dropped a field:
setForm({ name: 'Ada' }) // ❌ email is now undefined
setForm(prev => ({ ...prev, name: 'Ada' })) // ✅ keep the other fields
Using a form library
Libraries like React Hook Form handle this for you — they work with uncontrolled inputs by default and you set defaultValues once. For forms with more than a few fields, they save a lot of code. (React Hook Form)
Quick fix summary
| Situation | Fix |
|---|---|
useState() |
useState('') |
| Form object | Initialise every field |
| API data | value={field ?? ''} |
| Checkbox | useState(false), use checked |
| Partial state update | Spread previous state |
EasySpawn runs your React app with a live preview next to Claude Code, which can sweep a whole form for these warnings in one pass. See how it works or join the waitlist.
Related: React useState Explained · React Hook Form · Form Validation Explained · How to Add a Contact Form
Keep reading
"ReferenceError: window is not defined" in Next.js: How to Fix It
window, document and localStorage only exist in the browser. Next.js renders components on the server first, where they don't exist. Why it happens, and the fixes: useEffect, 'use client', typeof checks, dynamic imports with ssr: false, and libraries that touch window on import.
"Unexpected Token < in JSON at Position 0": What It Means and How to Fix It
Your code expected JSON and got HTML — almost always an error page or your app's index.html. Why it happens (wrong URL, 404, server error, SPA fallback, login redirect), how to see what the server actually sent, and how to parse responses safely.