跪拜 Guibai
← All articles
Frontend

React Form Data Flow: When State Should Own the Input and When the DOM Should

By 东风破_ ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
Controlled inputs pair `value` (or `checked`) with `onChange` so React state is the single source of truth.
Uncontrolled inputs store the value in the DOM and retrieve it via `ref.current.value`, typically at submit time.
Setting `value` without an `onChange` handler locks the input because React re-renders with the old state value.
Use `defaultValue` for initial content in uncontrolled inputs; switching to `value` later flips the component into controlled mode.
Multiple fields can share one change handler by giving each input a `name` that matches a state key and using `[name]: value` in the updater.
Real-time validation works cleanly with controlled forms because the same state drives inputs, error messages, and button disabled states.
File inputs are always uncontrolled because browsers forbid scripted value assignment for security reasons.
Never let a single input switch between controlled and uncontrolled across renders; initialize state with the correct type (string, boolean) from the start.
Conclusions

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.

Concepts & terms
Controlled component
A form element whose value is driven by React state. The component receives its current value as a prop and notifies changes via a callback like `onChange`, making React the single source of truth.
Uncontrolled component
A form element whose value is stored in the DOM itself. React reads the value imperatively through a ref, typically only when needed (e.g., on submit), rather than on every keystroke.
Computed property name
A JavaScript syntax (`[expression]: value`) inside an object literal that evaluates the expression to determine the property key. In forms, it lets a single change handler update the correct state field by using the input's `name` attribute as the key.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗