Menu

Controlled vs Uncontrolled Components in React

A controlled input gets its value from React state and changes only through onChange. An uncontrolled input keeps its own value in the DOM, and you read it with a ref or FormData. Learn the difference, the common warnings, and when to use each.

This page includes runnable editors - edit, run, and see output instantly.

A controlled component is a form element whose value lives in React state: you pass value and update the state in onChange, so React decides what the input shows. An uncontrolled component keeps its value in the DOM, like plain HTML: you pass a starting defaultValue and read the current value later through a ref or FormData.

Type in both inputs. The paragraph follows the first one on every keystroke, because each change sets state and renders again. The second input changes only in the DOM; React does not render, and you see its value only when "Read both" asks for it.

Controlled inputs

With a controlled input, React state is the single source of truth. The flow on every keystroke is:

  1. The user types, and the browser fires an input event.
  2. Your onChange handler reads e.target.value and calls the setter.
  3. React renders again and writes the new state back into the input's value.

Because your code sits in the middle of that loop, you can do things an uncontrolled input cannot: reject or reshape characters, show the value somewhere else, disable a button until the value is valid, or set the value from code (a "Clear" button, a suggestion the user clicks).

Try typing letters: they never appear, because the handler strips anything that is not a digit before setting state. The Verify button stays disabled until there are exactly six digits.

Controlled is what the form examples on the forms page use, and it is the default choice in most React code.

Uncontrolled inputs

An uncontrolled input works like plain HTML. React renders it once with defaultValue (or defaultChecked for checkboxes and radios) and then leaves it alone. To read the value, either keep a ref to the element or read all named fields from the form on submit with FormData.

There is no state for the fields and no onChange. The values are collected only when the form submits. Untick the checkbox and send again: the subscribe key disappears, because an unchecked checkbox is not part of FormData at all.

Changing defaultValue after the first render does nothing to the input, since React applies it only when the element is created. To reset an uncontrolled form, call e.target.reset(), or give the form a new key so React creates fresh inputs.

An input you cannot type in

If you pass value but no onChange, the input is controlled, but nothing ever updates the state. React writes the same value back after every keystroke, so the input looks frozen.

Try typing in each field. The first one does not change. The second one is the fix for a controlled input, and as you type there, the first and last fields follow it, because all three read the same state. The third accepts typing because it is uncontrolled. The fourth says on purpose that it cannot be edited.

In development, React logs a warning for the first input: "You provided a value prop to a form field without an onChange handler". The preview runs like a production build, so you will not see that warning here, but you will in your own dev server.

Changing from uncontrolled to controlled

React decides whether an input is controlled by looking at value. If value is undefined or null, the input is uncontrolled. So an input whose value starts as undefined and later becomes a string switches from uncontrolled to controlled during its life, and React warns in development: "A component is changing an uncontrolled input to be controlled".

The warning points at a real bug. Going back the other way, from a string to undefined, turns the input uncontrolled again, and React stops writing to it.

Type something in both inputs and press each Clear button. The second input empties. The first keeps its text: setting state to undefined made it uncontrolled, so React no longer controls what it shows.

The usual source of undefined is data that has not loaded yet (value={user.name} before the user arrives) or an object field that does not exist yet. Two fixes:

const [name, setName] = useState('');   // start with a string
<input value={user?.name ?? ''} />       // or fall back to one

For checkboxes the same rule applies to checked: start it at false, not undefined.

File inputs are always uncontrolled

An <input type="file"> cannot be controlled, because browsers do not let a page set its value (a page that could pick files on your disk would be a security hole). Read the chosen files from e.target.files in onChange, or through a ref when the form submits.

Choose a file or two from your computer; nothing is uploaded, the example only lists names and sizes. The one value you may set on a file input is the empty string, which is how the Clear button resets it.

When to choose which

ControlledUncontrolled
Value lives inReact stateThe DOM
Set the start valuevalue / checkeddefaultValue / defaultChecked
Read the valueAny time, from stateWith a ref, or FormData on submit
Renders on each keystrokeYesNo
Change the value from codeSet stateWrite to the DOM through a ref
Good forLive validation, formatting, dependent fieldsSimple forms read on submit, file inputs, form actions

Pick controlled when the screen has to react to the value while the user types. Pick uncontrolled when you only need the values at the end, which is also how React 19 form actions work: <form action={fn}> hands your function a FormData and resets the uncontrolled fields afterward.

Rendering on every keystroke sounds expensive, but for a normal form it is not a problem. It becomes one only when a large, slow tree re-renders with the input; then keep the state close to the input, or move the slow part into its own component.

The same two words describe your own components too. A component that gets its value and an onChange from its parent through props is controlled by the parent; one that keeps its own state is uncontrolled. Moving state up to make a component controlled is what lifting state up is about.

Frequently Asked Questions

What is a controlled component in React?

A form element whose current value comes from React state, set with value (or checked) and updated through onChange. React state is the single source of truth, so you always know the value and can change it from code.

What is an uncontrolled component in React?

A form element that keeps its own value in the DOM, like plain HTML. You give it a starting value with defaultValue (or defaultChecked) and read the current value when you need it, through a ref or FormData on submit.

Why can't I type in my React input?

You passed value without an onChange. The input is controlled, so React keeps putting the state value back after every keystroke. Add an onChange that updates the state, switch to defaultValue, or add readOnly if it really should not change.

How do I fix "A component is changing an uncontrolled input to be controlled"?

The input's value started as undefined (or null) and later became a string. Make sure it is always a string: start state at '', or write value={name ?? ''}.

Should I use controlled or uncontrolled inputs?

Use controlled inputs when the UI reacts to the value while the user types: validation, formatting, a disabled button, fields that depend on each other. Use uncontrolled inputs when you only need the values on submit, and for file inputs, which are always uncontrolled.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED