React Form Data Flow: When State Should Own the Input and When the DOM Should
Mispicking the pattern leads to unresponsive inputs, stale validation, or unnecessary re-renders. Knowing when to let the DOM hold the value avoids over-engineering simple forms and keeps complex ones predictable.
A controlled input binds `value` to React state and updates it via `onChange`, making state the single source of truth. An uncontrolled input skips the binding and stores the value directly in the DOM, retrieved through a ref only when needed. The difference determines whether real-time validation, field-to-field dependencies, and button states are straightforward or require manual synchronization. Multi-field forms can share a single change handler by matching each input's `name` attribute to a state key, using computed property names to update the correct field. Real-time validation becomes a natural side effect: the same state that drives the input also feeds error messages and disables the submit button. File inputs remain a hard exception because browsers block scripted value assignment, so they stay uncontrolled. Mixing modes within a single form is practical, but toggling a single input between controlled and uncontrolled across renders causes bugs. The decision hinges on when the value is consumed: if it feeds UI logic on every change, control it; if it's only needed at submit, let the DOM keep it.
A form doesn't need to be uniformly controlled or uncontrolled; text fields can be controlled while a file picker stays uncontrolled, and that hybrid is often the cleanest real-world pattern.
The practical litmus test is whether you find yourself writing `ref.current.value` only to immediately feed it into `setState` — if so, the component should have been controlled from the start.
React's insistence on `value` + `onChange` as a pair is less about ceremony and more about preventing the class of bugs where the DOM and React's virtual representation silently diverge.