Skip to content
Intermediate14 min study

Controlled vs. uncontrolled components

Decide who owns form input state — React or the DOM — and avoid mixing the two models in one input.

Question progress0 / 10 completed
Start the lesson
Controlled vs. uncontrolled components visual explanation

WHAT YOU WILL BE ABLE TO DO

Learning outcomes

  • Explain controlled vs. uncontrolled components in plain language.
  • Connect the behavior to the underlying browser or framework model.
  • Implement the core pattern and reason through edge cases.
  • Answer common follow-ups without relying on memorized phrases.

01Explain it simply

A controlled input's value is driven entirely by React state — value={state} plus onChange updates it — so React is the single source of truth. An uncontrolled input lets the DOM manage its own value internally, and React reads it only when needed, typically through a ref.

One-line definition: Decide who owns form input state — React or the DOM — and avoid mixing the two models in one input.

02Mental model

The dividing line is where truth lives. Controlled gives instant access to the current value for validation, formatting, and conditional UI on every keystroke, at the cost of a re-render per keystroke. Uncontrolled avoids that per-keystroke re-render and fits simple forms or integrating non-React widgets, but makes real-time reactive UI, like a live character counter, awkward since React doesn't know the value until it asks.

03Step by step

  • Decide whether the UI needs to react to every keystroke, such as validation, formatting, or character count.
  • Use a controlled input with value and onChange when it does.
  • Use an uncontrolled input with a ref and defaultValue when the value is only needed on submit.
  • Never assign value without a matching onChange — that produces a React warning and a frozen input.
  • Don't switch an input between controlled and uncontrolled across renders, such as value starting undefined then becoming a string.

04Working example

TSX
// Controlled — React owns the valuefunction ControlledEmail() {  const [email, setEmail] = useState('');  return <input value={email} onChange={(e) => setEmail(e.target.value)} />;}
// Uncontrolled — the DOM owns the value; React reads it on demandfunction UncontrolledEmail() {  const ref = useRef<HTMLInputElement>(null);  const handleSubmit = () => console.log(ref.current?.value);  return <input ref={ref} defaultValue="" />;}

The controlled input re-renders ControlledEmail on every keystroke because setEmail drives value directly — that's the cost of always knowing the current value. The uncontrolled input never triggers a React re-render while typing; the DOM keeps its own internal value, and the component only reads it through ref.current.value when handleSubmit actually needs it.

05Where it is used

  • Live validation, character counters, and conditional submit-button state with controlled inputs
  • Simple forms where only the final submitted value matters, using uncontrolled inputs
  • Integrating third-party non-React input widgets that manage their own DOM state
  • File inputs, which are always uncontrolled since their value can't be set programmatically

06Common mistakes

  • Passing value without onChange, producing a read-only input with a React console warning
  • Switching an input from uncontrolled, value={undefined}, to controlled after initial render, which React explicitly warns against
  • Defaulting to controlled for every field in a huge form and accepting needless re-renders when submit-time values would do
  • Forgetting that a file input can never be controlled — its value must be read via ref or the change event

07Interview answer

Frame the decision by who needs the current value and when, not as a stylistic preference — and know that switching a single input between the two models mid-lifecycle is the specific bug interviewers probe for.

Why does React warn when an input's value prop changes from undefined to a string across renders?

undefined as the value prop means the input started as uncontrolled, DOM-owned, and providing an actual string later means React tries to make it controlled — React warns because an input can't cleanly switch which side owns its value partway through its lifecycle.

DDConcept deep dives

Deep dive 1

The dividing line is where truth about the value lives

A controlled input's value is a direct reflection of React state — React re-renders the input with the current state on every change, making it the unambiguous source of truth. An uncontrolled input lets the browser's own DOM node track its value internally; React only asks for that value when it actually needs it, typically via a ref at submit time. Every other difference — re-render cost, live-validation capability, integration ergonomics — follows directly from this one structural choice.

  • Controlled: React always knows the current value, at the cost of a render per change.
  • Uncontrolled: the DOM owns the value; React reads it on demand and stays render-cheap while typing.
  • Choosing between them is choosing where the value's source of truth lives, not a stylistic preference.

Deep dive 2

Mixing the two models on one input is what actually breaks

Using both fields together correctly — some controlled, some uncontrolled, across different inputs in the same form — works fine and is common in performance-sensitive forms. What breaks is a single input trying to be both: passing value without onChange freezes the input and warns in the console, and switching an input's value prop between undefined and a defined string across renders triggers React's explicit uncontrolled-to-controlled warning, because React expects one ownership model to be chosen and kept consistent for that input's entire lifetime.

  • value without onChange makes an input read-only, usually unintentionally.
  • value starting as undefined and later becoming a string is the classic trigger for React's warning.
  • Different inputs in the same form can use different models without conflict.

Deep dive 3

The re-render cost of controlled inputs scales with form size and coupling

One controlled field triggering a re-render on every keystroke is rarely noticeable. The problem compounds when many controlled fields share one state object or one parent component — a single keystroke in any field then re-renders the entire form, including every other field's markup, even though only one value actually changed. Splitting controlled state into smaller, more local pieces, or converting submit-only fields to uncontrolled, are both direct responses to this specific scaling problem rather than general 'best practices' to apply everywhere.

  • A form's render cost under the controlled model scales with both field count and state coupling.
  • Splitting one big form-state object into per-field or per-section state limits the blast radius of each render.
  • Not every field in a large form needs to be controlled — only the ones that need live reactivity.

QAInterview questions and model answers

Attempt each answer aloud before opening it. The model answer shows the depth and precision expected in an interview; it is not a script to memorize.

Beginner · Coding · 1 min · Question 1What determines whether an input is controlled or uncontrolled?Open model answer

Model answer

Whether its value prop is driven by React state (controlled, React is the source of truth) or whether the DOM manages the input's value internally with React only reading it on demand, typically via a ref (uncontrolled).

JSX
// Controlled — React owns the value<input value={name} onChange={(e) => setName(e.target.value)} />
// Uncontrolled — the DOM owns it; read on demand<input ref={nameRef} defaultValue="" />
Open question page →
Intermediate · Conceptual · 1 min · Question 2What's the main cost of a controlled input?Open model answer

Model answer

Every keystroke updates React state, which triggers a re-render of the component holding that state — for a large form with many controlled fields, this can mean far more rendering work than the UI strictly requires.

Open question page →
Intermediate · Coding · 1 min · Question 3Why can't a live character counter be built cleanly with an uncontrolled input?Open model answer

Model answer

React has no visibility into the input's current value between renders when it's DOM-owned — a character counter needs to know the value on every keystroke to update its display, which is exactly what only a controlled input provides.

JSX
const [bio, setBio] = useState('');<>  <textarea value={bio} onChange={(e) => setBio(e.target.value)} maxLength={160} />  <span>{160 - bio.length} left</span></>
Open question page →
Intermediate · Coding · 1 min · Question 4What warning does React give when an input switches between controlled and uncontrolled?Open model answer

Model answer

It warns that a component is changing an uncontrolled input to be controlled, or vice versa, typically because the value prop starts as undefined and later becomes a defined string, or the reverse — React expects one model to be chosen and kept consistent.

JSX
// value is undefined on first render -> becomes a string later => warning<input value={user?.name} onChange={onChange} />// Fix: <input value={user?.name ?? ''} ... />
Open question page →
Intermediate · Conceptual · 1 min · Question 5Why is a file input always uncontrolled?Open model answer

Model answer

For security reasons, JavaScript cannot programmatically set a file input's value to an arbitrary file path — the browser only allows the user's own file-picker interaction to set it, so React can only read the selected file via a ref or the change event, never drive it via a value prop.

Open question page →
Intermediate · Conceptual · 1 min · Question 6When would you deliberately choose an uncontrolled input over a controlled one?Open model answer

Model answer

When the form is simple, values are only needed at submit time, or you're integrating a third-party widget that already manages its own internal DOM state and would conflict with React also trying to own that same value.

Open question page →
Beginner · Conceptual · 1 min · Question 7Can a single form mix controlled and uncontrolled inputs?Open model answer

Model answer

Yes — it's common for most fields to be uncontrolled while one or two fields that need live reactivity, like an inline-validated email field, are controlled; the two models coexist fine as long as no single input mixes them.

Open question page →
Advanced · Conceptual · 1 min · Question 8How does defaultValue differ from value for an input?Open model answer

Model answer

defaultValue sets only the input's initial value and then lets the DOM own subsequent changes, making the input uncontrolled; value continuously dictates the current value from React state, making the input controlled — using both on one input is a conflicting signal.

Open question page →
Advanced · Conceptual · 1 min · Question 9Why might a form library like React Hook Form favor uncontrolled inputs internally?Open model answer

Model answer

Registering inputs as uncontrolled and reading values via refs avoids a re-render on every keystroke across potentially dozens of fields, which is a major reason such libraries report better performance for very large forms.

Open question page →
Beginner · Conceptual · 1 min · Question 10What React hook is commonly used to read an uncontrolled input's value only when needed, without subscribing to every change?Open model answer

Model answer

useRef — it creates a persistent reference to the DOM node whose .value can be read on demand, such as inside a submit handler, without causing any re-render when the underlying DOM value changes.

Open question page →

SCScenario questions

Scenario 1

A 40-field settings form is entirely controlled, and users on lower-end devices report the form feels laggy while typing in any field.

  1. Confirm each keystroke in any field re-renders the whole form component.
  2. Decide which fields truly need live reactivity, such as ones with inline validation.
  3. Convert fields that only need their value at submit time to uncontrolled inputs with refs.
  4. Optionally split the controlled fields into smaller subcomponents to limit re-render scope.
Reveal worked answer

I would first confirm the lag comes from the whole 40-field form re-rendering on every keystroke because all fields share one state object. For fields with no live validation or dependent UI, I'd convert them to uncontrolled inputs read via ref at submit time, keeping only fields that genuinely need per-keystroke reactivity as controlled. I would also consider splitting the controlled fields into smaller components so a change in one doesn't re-render the other thirty-nine.

Verify and go deeper