# React: “A component is changing an uncontrolled input to be controlled”

> React warns A component is changing an uncontrolled input to be controlled: value started as undefined or null. Start it as an empty string, or use value ?? ''.

Canonical: https://app369.com/fix/react-changing-an-uncontrolled-input-to-be-controlled/
Last updated: 2026-11-08

The console warns A component is changing an uncontrolled input to be controlled, usually on a form that fills in from an API or from state that starts empty. On the first render the input’s value was undefined or null, so React treated it as an input it doesn’t manage. When the data arrived, value became a string and React was asked to switch modes.

**Short answer**

Make value a string from the first render. Start the state as '' instead of undefined, or write value={field ?? ''} where the data may be missing. For a checkbox, keep checked a boolean with checked={done ?? false}. The reverse warning, changing a controlled input to be uncontrolled, means the value went from a string back to undefined, often after a form reset that set a field to undefined.

## **Where the undefined value comes from, and the fix**

1. 01 ### **State that starts as undefined**
  > “A controlled component should always receive a string value, not null or undefined.” [**React, <input>**](https://react.dev/reference/react-dom/components/input) useState() with no argument, or a form object without every field, gives the input undefined on the first render. Give each field an empty string as its starting value.```
// warns: email is undefined at first
const [email, setEmail] = useState();

// works: always a string
const [email, setEmail] = useState('');
<input value={email} onChange={(e) => setEmail(e.target.value)} />
```
2. 02 ### **Data from an API or database that arrives later**
  > “You cannot pass value={undefined} first and later pass value="some string" because React won’t know whether you want the component to be uncontrolled or controlled.” [**React, <input>**](https://react.dev/reference/react-dom/components/input) A form filled from a fetch, Firestore or Supabase row has undefined fields until the response comes in, and null for empty columns after it. Fall back to an empty string where the value is read. A value of null also gets its own warning, value prop on input should not be null. With react-hook-form or Formik, give every field a starting value in defaultValues or initialValues.```
<input
  value={profile?.phone ?? ''}
  onChange={(e) => setProfile({ ...profile, phone: e.target.value })}
/>
```
3. 03 ### **A checkbox whose checked is undefined**
  > “Similarly, if you pass checked to a checkbox, ensure it’s always a boolean.” [**React, <input>**](https://react.dev/reference/react-dom/components/input) checked={item.done} on an item without a done field is undefined until it is set. Use checked={item.done ?? false}, or !!item.done, so it is always true or false.
4. 04 ### **Mixing value and defaultValue**
  > “An input can’t be both controlled and uncontrolled at the same time.” [**React, <input>**](https://react.dev/reference/react-dom/components/input) Pick one mode per input. Use value with onChange when your code needs the text as the user types, or defaultValue with no value when you only read it on submit. Switching between the two triggers this warning; passing both on one input triggers a related one.

Checked against the vendor’s docs on November 8, 2026: [React reference: <input> (troubleshooting the controlled input warning)](https://react.dev/reference/react-dom/components/input).

## **Rather hand it over?**

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

- [from $450**App Fix Ticket**One reproducible bug in an existing mobile or web app, diagnosed and fixed.**See scope and start**](https://app369.com/fixed-price/flutter-firebase-bug-fix/)
- [from $450**AI-Built App Rescue**A Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.****No issues worth fixing, no charge.**** If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.**See scope and start**](https://app369.com/fixed-price/ai-app-rescue/)

Related: [**React “Rendered more hooks than during the previous render”**](https://app369.com/fix/react-rendered-more-hooks-than-during-the-previous-render/) · [**React “Too many re-renders”**](https://app369.com/fix/react-too-many-re-renders/) · [**React “Each child in a list should have a unique key”**](https://app369.com/fix/react-each-child-unique-key/)

Questions

## **More on this error**

<details>

<summary>**Is it only a warning? **+</summary>

It is a warning, but it points at a real bug: while the value is undefined, React isn’t managing the input, so text the user typed can be replaced when the data arrives. Fixing the starting value removes both.

</details>

<details>

<summary>**Does it apply to textarea and select? **+</summary>

The same rule applies: keep their value a string. The warning text names input, but a textarea or select whose value is undefined is also left unmanaged by React until the value arrives.

</details>

<details>

<summary>**Can you fix it for me? **+</summary>

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

</details>

A different error?

## **Describe it and get a written quote**

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.

[**Describe your problem **→](https://app369.com/contact/) [**All fixes**](https://app369.com/fix/)