Controlled forms make React state the source of truth for every keystroke. Uncontrolled forms flip that, letting the DOM manage input values and reading them only on demand via refs or the FormData API. This lesson covers when that tradeoff pays off.
1Letting the DOM Hold the Value
In a controlled form, React state is the single source of truth for every field's value, updated on every keystroke. An uncontrolled form flips this: the DOM input manages its own value internally, and the value is only read — typically via a ref — at the exact moment it's needed, like submission.
2Reading Values with a Ref
An uncontrolled input needs no value or onChange prop — just a ref and, optionally, a defaultValue for its initial content. The current value is read directly from the ref's current.value property whenever needed, typically inside a submit handler.
3The FormData API
For an entire form of uncontrolled fields, the native FormData API reads every named input's current value from the form element directly, given to the submit handler's event, without needing a separate ref on every single field.
4When Uncontrolled Wins
Uncontrolled forms are well suited to large, simple forms with no real-time validation or per-keystroke formatting needs — they trigger fewer re-renders, require less code, and integrate naturally with native HTML form submission and React 19's Actions and Server Actions.
5Step-by-Step Breakdown
Letting the DOM Hold the Value. You've already mastered controlled forms, where React's state is the single source of truth for every keystroke. An uncontrolled form flips that: the actual DOM input holds its own value, and you only reach in and read it — typically via a ref — at the moment you actually need it, like on submit.
Reading Values with a Ref. An uncontrolled input needs no value or onChange prop at all — just a ref and, optionally, a defaultValue for its starting content. You read the current value directly from inputRef.current.value whenever you need it, typically inside a submit handler.
In an uncontrolled input using ref={emailRef} with no value prop, what triggers a React re-render as the user types?
- →Nothing — the DOM manages the value internally, with no React re-render per keystroke
- →React re-renders on every keystroke, same as a controlled input
The FormData API. For a whole form of uncontrolled inputs, you don't need a ref on every single field — the native FormData API can read every named input's value from the form element itself, given to the submit handler's event. This pairs especially well with React 19's Actions.
When Uncontrolled Wins. Uncontrolled forms shine for large, simple forms with no real-time validation or formatting needs — fewer re-renders (better for forms with dozens of fields), less code, and a more natural fit with native HTML form submission and React 19's Actions and Server Actions.
Which is a better fit for the uncontrolled pattern: a 20-field signup form submitted once, or a live search box filtering results as you type?
- →The 20-field signup form, since it doesn't need per-keystroke reactions
- →The live search box, since it needs to react to every keystroke
Mastery Achieved. You now understand uncontrolled forms: letting the DOM hold input values, reading them with refs or the FormData API, and recognizing when this pattern's simplicity and performance beat a fully controlled approach. Next, you'll learn React Hook Form, the library that formalizes uncontrolled forms with validation built in.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
FormData is fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Uncontrolled Inputs Still Need Proper Labels and Error Association
Because uncontrolled inputs skip React-driven live feedback, it's especially important to ensure standard HTML accessibility (label htmlFor/id association, aria-invalid, aria-describedby for errors) is present, since there's no controlled re-render to lean on for dynamic ARIA updates.
SEO Implications
- 1
Uncontrolled Forms Pair Naturally with Progressive Enhancement
Because uncontrolled forms rely on native HTML form submission semantics, they can work even before client JavaScript fully hydrates in a server-rendered app, which benefits both resilience and initial interactivity metrics.
Best Practices
Use FormData for Multi-Field Forms Instead of a Ref Per Field
Reading every field through FormData(formElement) after submission is simpler and less repetitive than creating and attaching a separate ref for each individual input.
Reserve Uncontrolled Forms for Cases Without Live Feedback Needs
If a form needs real-time validation, character counters, or conditional field visibility based on live input, a controlled (or React Hook Form-managed) approach is usually a better fit than pure refs.
Frequent Bugs
Trying to read an uncontrolled input's value immediately after a state update, but getting a stale result.
Uncontrolled input values live entirely in the DOM, not React state — read them directly from the ref (or via FormData) at the moment they're needed, rather than trying to track them through a React state variable.
A defaultValue passed to an uncontrolled input doesn't update when its underlying prop changes.
defaultValue, as its name implies, only sets the initial value — the DOM owns the field afterward and won't sync to later prop changes. If the value genuinely needs to update programmatically after the initial render, the field should be controlled instead.
Real-World Examples
A Large Uncontrolled Signup Form
A 15-field signup form has no real-time validation requirements — errors are only shown after submission. Building it with uncontrolled inputs and reading all fields via FormData(e.target) on submit avoids the re-render overhead of 15 separate pieces of controlled state, while keeping the implementation simple.
function SignupForm() {
function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const payload = Object.fromEntries(formData.entries());
submitSignup(payload);
}
return (
<form onSubmit={handleSubmit}>
<input name="firstName" /> <input name="lastName" /> <input name="email" />
{/* ...more fields, no state needed for any of them */}
</form>
);
}