Explain the difference between controlled and uncontrolled components with examples.
A controlled component's value lives in React state and is driven by props (value + onChange) — React is the single source of truth. An uncontrolled component keeps its own state in the DOM, read via a ref when needed (defaultValue). Controlled = predictable; uncontrolled = less code, fewer re-renders.
The distinction is about who owns the input's value — React state, or the DOM.
Controlled component
React state is the single source of truth. The input's value is a prop; every change goes through onChange → setState → re-render.
function ControlledInput() {
const [name, setName] = useState("");
return (
<input value={name} onChange={(e) => setName(e.target.value)} />
);
}- The DOM input can't hold a value React doesn't know about.
- You can transform/validate/format on every keystroke, disable the submit button reactively, sync with other state.
Uncontrolled component
The DOM keeps its own state. React doesn't track each keystroke; you read the value when you need it (usually on submit) via a ref. Initial value via defaultValue (not value).
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => console.log(inputRef.current.value);
return <input defaultValue="" ref={inputRef} />;
}Comparison
| Controlled | Uncontrolled | |
|---|---|---|
| Source of truth | React state | DOM |
| Value access | always available in state | read via ref on demand |
| Re-renders | on every keystroke | none from typing |
| Instant validation/formatting | easy | hard |
| Code | more | less |
| File inputs | must be uncontrolled | — |
When to use which
- Controlled — you need the value during editing: live validation, formatting (currency, phone), conditional UI, dependent fields, controlled clearing.
- Uncontrolled — simple forms where you only need the value on submit;
<input type="file">(always uncontrolled); integrating with non-React code; performance-sensitive large forms.
The performance angle
A controlled useState per field re-renders on every keystroke. For large forms this matters — which is exactly why React Hook Form uses uncontrolled inputs under the hood (refs) to avoid those re-renders while still giving you validation. So in practice: controlled for small/interactive forms, RHF (uncontrolled) for big ones.
Gotcha
Passing value without onChange makes a read-only input and warns. Switching a component between controlled and uncontrolled (value going undefined → defined) also warns — pick one and keep value always defined.
Follow-up questions
- •Why does React Hook Form use uncontrolled inputs?
- •Why must file inputs be uncontrolled?
- •What warning do you get switching between controlled and uncontrolled, and why?
- •How would you do live input formatting (e.g. currency)?
Common mistakes
- •Passing value without onChange and getting a read-only input.
- •Letting value go from undefined to defined, flipping controlled/uncontrolled.
- •Using controlled state per field on huge forms and causing re-render lag.
- •Trying to control a file input.
Performance considerations
- •Controlled inputs re-render the component on every keystroke; on large forms this is noticeable. Uncontrolled inputs (refs) avoid that — the basis of React Hook Form's performance. Choose based on whether you need the value mid-edit.
Edge cases
- •File inputs — always uncontrolled.
- •Programmatically clearing or setting a value.
- •Integrating with a non-React widget that owns its own DOM state.
- •Initial value arriving asynchronously.
Real-world examples
- •Controlled: a search box filtering results live, a currency input formatting as you type.
- •Uncontrolled: a simple contact form read on submit; React Hook Form's ref-based fields.