A form is the one place where the browser already has state of its own. Every <input> keeps a value, every <form> can serialize itself into FormData, and the platform validates required fields without a line of JavaScript. React 7,897 can take all of that over, hand all of it back, or split the difference — and picking the wrong split is the usual reason a login screen turns into four hundred lines.
React 19 changed the arithmetic. Passing a function to a form's action prop makes React call it with the submitted FormData, wrapped in a transition, with the fields reset afterwards; useActionState, useFormStatus and useOptimistic then give you the pending flag, the returned result and an instant preview for free. Three years ago every one of those needed a state variable and a try/finally.
| Approach | Where the value lives | Best for |
|---|---|---|
| Controlled input | React state | Live formatting, dependent fields |
| Uncontrolled input | The DOM node | Large forms, file inputs |
| Form action + FormData | The DOM, read on submit | Submit-once forms of any size |