ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

Controlled vs Uncontrolled Components in React

Short answer

In a controlled component React state owns the input's value, passed via `value` and updated in `onChange`. In an uncontrolled component the DOM owns it, set once with `defaultValue` and read later with a ref. Controlled is the default recommendation; uncontrolled is for simple forms and file inputs.

ControlledUncontrolled
Source of truthReact stateThe DOM node
Set the value withvalue={state}defaultValue={…}
Read the value withThe state variableA ref → ref.current.value
Needs onChange?Yes — requiredNo
Re-renders per keystrokeYesNo
Instant validationEasyAwkward
Best forMost forms; anything reactiveSimple forms, file inputs, non-React integration
The distinction is simply: who is the source of truth for the value?

The difference in one line

A controlled component has its value driven by React state — the input displays whatever state says, and state only changes when you tell it to in onChange. An uncontrolled component lets the browser keep the value internally, exactly as a plain HTML form does, and you reach in to read it when you need it.

Everything else — the warnings, the performance characteristics, the validation ergonomics — follows from that single question of ownership.

A controlled input

Controlled: React owns the valuejsx
1function NameField() {
2  const [name, setName] = useState("");
3
4  return (
5    <input
6      value={name}                              // React drives what's displayed
7      onChange={(e) => setName(e.target.value)} // and is the only way it changes
8    />
9  );
10}
11// Type "ab" → renders twice, name === "ab"

The loop is: user types → onChange fires → setState → re-render → input displays the new state. React is in the middle of every keystroke, which is exactly what makes controlled inputs so flexible — you can transform, validate or reject input as it is typed.

Why that middle step is usefuljsx
1// Force uppercase, and refuse anything over 10 characters —
2// impossible to do this cleanly with an uncontrolled input.
3<input
4  value={code}
5  onChange={(e) => {
6    const next = e.target.value.toUpperCase();
7    if (next.length <= 10) setCode(next);
8  }}
9/>
10// Type "abc" → displays "ABC"

An uncontrolled input

Uncontrolled: the DOM owns the valuejsx
1function NameField() {
2  const inputRef = useRef(null);
3
4  function handleSubmit(e) {
5    e.preventDefault();
6    console.log(inputRef.current.value);  // → "whatever the user typed"
7  }
8
9  return (
10    <form onSubmit={handleSubmit}>
11      <input ref={inputRef} defaultValue="Arun" />
12      <button>Submit</button>
13    </form>
14  );
15}
16// Typing causes ZERO re-renders

Note defaultValue rather than value. It sets the initial value and then steps out of the way. If you used value here without an onChange, React would make the field read-only and warn you — which is the first of the two warnings everyone hits.

The two warnings everyone hits

The fix: never let value be undefinedjsx
1// BAD — user is undefined until the fetch resolves
2const [user, setUser] = useState();
3<input value={user?.name} onChange={…} />
4// → "changing an uncontrolled input to be controlled"
5
6// GOOD — always a string, from the very first render
7const [name, setName] = useState("");
8<input value={name} onChange={(e) => setName(e.target.value)} />
9
10// Also fine: coerce at the boundary
11<input value={user?.name ?? ""} onChange={…} />

This is 1 of 75+ questions in the React Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the React Interview Kit → ₹399

File inputs are always uncontrolled

This is the exception interviewers love, because it has a genuine security reason behind it. You cannot set the value of an <input type="file"> from JavaScript — if you could, a malicious page could point the field at /etc/passwd and silently upload it when the user submitted an unrelated form. The value is read-only by design, so the input can only ever be uncontrolled.

Reading a file inputjsx
1function Upload() {
2  const fileRef = useRef(null);
3
4  function submit() {
5    const file = fileRef.current.files[0];
6    console.log(file.name);  // → "resume.pdf"
7  }
8
9  return <input type="file" ref={fileRef} onChange={submit} />;
10}
11// You may READ .files, but assigning .value throws in every browser

Which should you actually use?

The React docs recommend controlled inputs for most cases, and that is the right default. Reach for uncontrolled when you have a specific reason.

  • Use controlled when the UI reacts to the value as it changes — live validation, a character counter, a disabled submit button, a dependent field, or a search box that filters as you type.
  • Use uncontrolled for a simple form you only read on submit, when you're integrating a non-React widget that manages its own DOM, or for a file input, where you have no choice.
  • Use uncontrolled for very large forms where a re-render on every keystroke of every field measurably hurts — though a library is usually the better answer there.

The performance argument, honestly

Every keystroke in a controlled input triggers a state update and a re-render. People hear that and assume controlled inputs are slow. For a login form with three fields, the difference is unmeasurable — React is fast, and you should not contort your code to avoid it.

It becomes real when a single keystroke re-renders a large tree: a 40-field form where every field lives in one parent component, so typing in any one of them re-renders all forty. The fix is usually not "switch to uncontrolled" but one of these:

  1. Move each field's state down into the field component, so a keystroke only re-renders that field.
  2. Use a form library — react-hook-form is uncontrolled by default and exists precisely for this.
  3. Debounce the expensive downstream work (the search request, the validation call), not the input itself.

Checkboxes, radios and selects

Text inputs get all the attention, but the other form controls each have their own controlled/uncontrolled pairing, and interviewers sometimes ask about them specifically to see whether you learned the pattern or just memorised one case.

ControlControlledUncontrolled
text / textareavaluedefaultValue
checkbox / radiocheckeddefaultChecked
selectvalue on <select>defaultValue on <select>
multi-selectvalue={array}defaultValue={array}
file— (impossible)always uncontrolled
The controlled prop and its uncontrolled counterpart for each control.
Two things that surprise peoplejsx
1// 1. A checkbox uses checked, not value. Passing value does nothing useful.
2<input type="checkbox" checked={agreed} onChange={e => setAgreed(e.target.checked)} />
3//                                                              ^^^^^^^ not .value
4
5// 2. React puts value on <select>, unlike HTML's selected on <option>.
6<select value={city} onChange={e => setCity(e.target.value)}>
7  <option value="chennai">Chennai</option>
8  <option value="bengaluru">Bengaluru</option>
9</select>
10// → far less error-prone than managing "selected" on each option

The checkbox one catches people regularly: e.target.value on a checkbox returns the value attribute, which is the string "on" unless you set it. The boolean you actually want is e.target.checked.

Radio groups are the other awkward case. Every radio in a group shares a name, and each one's checked prop compares its own value against the single piece of state — so one state variable drives the whole group rather than one per button.

One state value for a whole radio groupjsx
1const [plan, setPlan] = useState("basic");
2
3["basic", "pro"].map(p => (
4  <label key={p}>
5    <input
6      type="radio"
7      name="plan"
8      value={p}
9      checked={plan === p}                  // ← compare, don't store per-button
10      onChange={e => setPlan(e.target.value)}
11    />
12    {p}
13  </label>
14));
15// → selecting "pro" sets plan to "pro"; the other radio unchecks itself

What React 19 changes

React 19's form actions make well-built uncontrolled forms more attractive than they used to be. You can pass a function directly to a form's action prop and receive FormData, with no per-field state at all — the platform does the collecting.

React 19 form action — uncontrolled by designjsx
1function Signup() {
2  async function signup(formData) {
3    const email = formData.get("email");  // → "arun@example.com"
4    await createUser({ email });
5  }
6
7  return (
8    <form action={signup}>
9      <input name="email" type="email" />
10      <button type="submit">Sign up</button>
11    </form>
12  );
13}
14// No useState, no onChange, no ref — and pending state via useFormStatus

This does not make controlled inputs obsolete. Anything genuinely reactive still wants React state. But it does mean "uncontrolled" is no longer the slightly-unfashionable option it was a few years ago, and saying so shows you are current.

Can one form mix controlled and uncontrolled inputs?

Yes. The controlled/uncontrolled distinction is per input, not per form. A form with a controlled search field and an uncontrolled file input is completely normal — in fact it's unavoidable, since file inputs can't be controlled.

What's the difference between value and defaultValue?

value binds the input to React state on every render and makes it controlled. defaultValue only sets the initial DOM value on the first render and is then ignored — changing it later does nothing to an already-rendered input. For checkboxes and radios the equivalent pair is checked and defaultChecked.

How do you reset an uncontrolled form?

Call form.reset() on the form element, which restores every field to its defaultValue. The React-flavoured alternative is to change the form's key prop, which unmounts and remounts the whole subtree with fresh defaults.

Is a component with useState internally 'controlled'?

No — that's a different sense of the word and a common source of confusion. Controlled/uncontrolled describes whether the value comes from the parent via props. A component holding its own useState is uncontrolled from its parent's perspective, however much state it manages internally. Many library components support both, accepting either value (controlled) or defaultValue (uncontrolled).

Frequently asked questions

What is a controlled component in React?
A component whose form value is driven by React state. You pass the state into the input's value prop and update it in onChange, so React state is the single source of truth and the input can never hold a value React doesn't know about.
What is an uncontrolled component in React?
A component that lets the DOM keep the form value internally, the way plain HTML does. You optionally seed it with defaultValue and read it later through a ref — for example ref.current.value on submit. Typing causes no re-renders.
What is the difference between controlled and uncontrolled components?
Who owns the value. Controlled components store it in React state and require an onChange handler; uncontrolled components leave it in the DOM and are read via a ref. Controlled gives you reactivity and validation as the user types; uncontrolled gives you fewer renders and less code.
Which is better, controlled or uncontrolled?
Controlled is the better default and what the React docs recommend, because it makes the value available to the rest of your UI. Choose uncontrolled when the form is simple and only read on submit, when integrating a non-React widget, or for file inputs, which cannot be controlled.
Why can't file inputs be controlled?
Because their value is read-only in every browser. If JavaScript could set it, a page could point a file field at an arbitrary path and upload the user's private files without their knowledge. You can read ref.current.files, but you can never assign the value, so a file input is always uncontrolled.
How do I fix 'A component is changing an uncontrolled input to be controlled'?
The value prop started as undefined or null and later became a string. Initialise the state to an empty string instead of leaving it undefined, or coerce at the point of use with value={data?.name ?? ""}. React decides the input's mode on the first render and cannot switch it afterwards.
What's the difference between value and defaultValue?
value binds the input to React state on every render and makes it controlled — it requires onChange. defaultValue sets only the initial DOM value and is ignored on later renders, keeping the input uncontrolled. For checkboxes and radio buttons, the equivalents are checked and defaultChecked.
Are controlled components slower?
Each keystroke triggers a state update and a re-render, but for a normal form that is unmeasurable. It only matters when one keystroke re-renders a large tree — a 40-field form all held in one parent. The fix is usually to push state down into each field or use a library like react-hook-form, not to abandon controlled inputs.
Can I mix controlled and uncontrolled inputs in one form?
Yes — the distinction applies per input, not per form. A controlled text field alongside an uncontrolled file input is completely standard, and unavoidable if the form has a file upload.
How do I reset an uncontrolled form?
Call form.reset() on the form element to restore every field to its defaultValue, or change the form's key prop so React unmounts and remounts the subtree with fresh defaults.
Do React 19 form actions replace controlled components?
No, but they make uncontrolled forms much more attractive. Passing a function to a form's action prop gives you FormData on submit with no per-field state, and useFormStatus provides pending state. Anything genuinely reactive as the user types still calls for controlled inputs.
Is a component that uses useState internally a controlled component?
No — that's a different meaning of the word. Controlled/uncontrolled describes whether the value is supplied by the parent through props. A component managing its own useState is uncontrolled from the parent's point of view. Well-designed library components usually support both, accepting either value or defaultValue.

This is 1 of 75+ questions in the React Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the React Interview Kit → ₹399
Written by Arun Karthikeyan · Last updated

Full kit

React Interview Kit · ₹399

Get it →