Controlled vs uncontrolled inputs: who owns the value?
The controlled-versus-uncontrolled debate sounds like a framework quirk and is really one clean question: who owns the input’s value? In a controlled input, your component’s state owns it — the value comes from state, and every keystroke goes through a handler that updates state, which re-renders the input. In an uncontrolled input, the DOM owns it — the browser tracks the value internally and you read it only when you need it. Neither is “correct”; they are two ownership models, and the right choice is per field, driven by whether you need to react to the value as it changes.
Controlled: state owns the value
A controlled input has its value bound to state and an onChange that updates it.
The state is now the single source of truth, which means you can react to every
change — validate live, format as-you-type, enable a button, mirror the value
elsewhere:
function Search() {
const [q, setQ] = useState("");
return (
<input
value={q} // value comes FROM state
onChange={(e) => setQ(e.target.value)} // every keystroke updates state
/>
);
// now `q` is available to filter, validate, or debounce on each change
}
The cost is a render per keystroke and the discipline of keeping the loop closed —
forget the onChange and the input appears frozen, because state never updates.
Uncontrolled: the DOM owns the value
An uncontrolled input lets the browser hold the value; you grab it with a ref only when you need it, typically on submit. There is no per-keystroke render and no state to manage:
function SignupForm() {
const email = useRef(null);
const onSubmit = (e) => {
e.preventDefault();
sendSignup(email.current.value); // read the DOM value once, on submit
};
return <form onSubmit={onSubmit}><input ref={email} defaultValue="" /></form>;
}
Note defaultValue, not value: you seed the initial value but do not bind it, so
the DOM stays in charge.
Choose per field by “do I need to react?”
The decision rule is simple and it is per field, not per app. Use controlled
when you need to respond to the value as it changes — live validation, formatting,
a dependent field, a character counter, disabling submit until valid. Use
uncontrolled when you only need the value at the end and want to avoid the
render churn — a large form of plain fields, a file input (which is always
uncontrolled), integrating a non-React widget. Many real forms mix the two: an
email field that validates live is controlled, while the twelve plain text fields
beside it are uncontrolled for performance. The trap to avoid is switching an input
between the two across renders (a value that is sometimes undefined), which
makes React warn and behave erratically — pick an owner per field and keep it. The
form-field-molecule exercise builds a field that supports both modes cleanly, which
is the clearest way to internalise that “controlled or not” is an ownership choice,
not a rule.