Blog
3 min read

"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

(React useState explained)

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