Controlled vs. Uncontrolled Components in React

In Brief
Controlled inputs keep their current value in React state, using value or checked alongside onChange. Uncontrolled inputs leave the current value with the DOM, using defaultValue or defaultChecked when an initial value is needed. Neither approach is inherently better: controlled inputs suit interfaces that need to react to changes immediately, whilst uncontrolled inputs can be simpler when the value only matters when it is read or submitted.
React forms are where many developers first discover that rendering HTML with a component model is not quite the same thing as just writing HTML.
An ordinary browser form can happily keep track of its own input values in the DOM. React, however, also gives us the option of treating those values as component state. That leads to the distinction between controlled and uncontrolled components.
The names sound slightly abstract, but the core difference is straightforward.
A controlled input gets its current value from React state.
An uncontrolled input keeps its own current value in the DOM, and React only reads it when needed.
Neither approach is automatically right in every situation. The useful question is what kind of control the interface actually needs.
April 2026: During restoration, I rewrote the examples with TypeScript and React Hooks. React 16.8 introduced Hooks in February 2019, so the useState‑ and useRef‑based examples post‑date this article's original July 2015 publication; the controlled/uncontrolled distinction itself is older.
A Controlled Input is Driven by React State
This is the pattern most React developers see first:
const SearchForm = (): JSX.Element => { const [query, setQuery] = useState(''); return ( <input value={query} onChange={(event) => setQuery(event.target.value)} /> );};The displayed value comes from query, and every keystroke updates that state through onChange.
That means React is the source of truth.
Why Controlled Inputs are so Popular
Once the value lives in state, React can do much more with it easily:
- validate as the user types
- show character counts
- enable or disable buttons
- conditionally render helper text
- transform the value before display
- keep several UI elements in sync
For example:
const SignupForm = (): JSX.Element => { const [email, setEmail] = useState(''); const isValid = email.includes('@'); return ( <> <input value={email} onChange={(event) => setEmail(event.target.value)} /> {!isValid && <p>Please enter a valid email address.</p>} <button disabled={!isValid}>Continue</button> </> );};This kind of immediate feedback is exactly where controlled components feel strong.
An Uncontrolled Input Keeps Its Value in the DOM
With an uncontrolled component, React does not keep the live value in state.
Instead, we usually read it through a ref when we need it:
const SearchForm = (): JSX.Element => { const inputRef = useRef<HTMLInputElement | null>(null); const handleSubmit = (event: FormEvent<HTMLFormElement>): void => { event.preventDefault(); console.log(inputRef.current?.value ?? ''); }; return ( <form onSubmit={handleSubmit}> <input ref={inputRef} defaultValue="" /> <button type="submit">Search</button> </form> );};Now the browser is managing the input's current value, not React state.
This is Closer to Traditional HTML Form Behaviour
That can be useful when:
- the input is simple
- you only need the value on submit
- live validation is unnecessary
- you are integrating with non‑React code
- you want less state bookkeeping
In other words, uncontrolled inputs are not some second‑rate fallback. Sometimes they are simply the lighter option.
value vs. defaultValue: Controlled and Uncontrolled Inputs
This distinction matters.
For controlled inputs, we typically use:
value={stateValue}For uncontrolled inputs, we usually use:
defaultValue="initial text"defaultValue sets the initial value, but after that the DOM keeps control.
If you use value without updating it correctly, the input becomes effectively frozen because React keeps reapplying the same value.
Checkboxes Follow the Same Pattern
Checkboxes use different properties, but the distinction between controlled and uncontrolled inputs is the same.
For a controlled checkbox, use checked and update the state through onChange:
const NewsletterCheckbox = (): JSX.Element => { const [subscribed, setSubscribed] = useState(false); return ( <input type="checkbox" checked={subscribed} onChange={(event) => setSubscribed(event.target.checked)} /> );};Here, React state determines whether the checkbox is checked. Notice that the new value comes from event.target.checked, rather than event.target.value.
For an uncontrolled checkbox, the equivalent of defaultValue is defaultChecked:
<input type="checkbox" defaultChecked />defaultChecked sets the initial state of the checkbox, but does not continue controlling it after that. The browser takes over from there.
This means the same basic distinction applies to both kinds of input: value and checked are used when React controls the current state, whilst defaultValue and defaultChecked provide an initial state for an input that remains uncontrolled.
There is another useful consequence of this distinction. If you provide checked without an onChange handler, the checkbox will appear read‑only because you have told React what its checked state must be without providing a way for the user's interaction to update that state.
If that is intentional, make it explicit with readOnly. Otherwise, a controlled checkbox should normally pair checked with an onChange handler that updates the state.
Controlled Components Make State Explicit
That is their biggest strength.
The current form value is not hidden inside the browser. It is part of the component model, so the rest of the UI can respond to it deliberately.
This is especially useful for:
- complex forms
- conditional validation rules
- multi‑step flows
- filtering interfaces
- search and autocomplete
When the value affects other parts of the interface immediately, keeping it in React state is usually worth the extra ceremony.
Uncontrolled Components Reduce Overhead in Simpler Cases
Suppose we have one newsletter email field and only care about the value when the form is submitted.
Building a whole state‑update cycle for every keystroke may not buy us much. An uncontrolled input plus a ref can be perfectly reasonable.
The trick is not to treat every form as though it must be fully controlled by default.
File Inputs are the Famous Special Case
<input type="file" /> is typically treated as uncontrolled because the browser owns that value and React does not set it in the same way as normal text inputs.
That is a good reminder that the DOM still has its own rules. React gives us useful abstractions, but it does not erase the browser underneath.
A Common Mistake is Mixing the Two Approaches Carelessly
Developers often run into warnings when an input starts uncontrolled and later becomes controlled, or the other way around.
For example, an input might begin with undefined, then later receive a string value. React notices the mode changing and complains because the component's behaviour has become inconsistent.
The fix is usually to choose one model clearly from the start.
If it is controlled, initialise the value properly:
const [name, setName] = useState('');That way the input is controlled from the first render onwards.
The Question is Not "Which is More React"
Controlled components are often presented as the "proper" React approach. That is too simplistic.
The better question is:
How much influence does the rest of the UI need over this value whilst the user is editing it?
If the answer is "a lot", controlled components usually win.
If the answer is "hardly any", uncontrolled inputs may be a better fit.
Practical Guidance
Use controlled inputs when:
- the UI depends on the current value immediately
- validation happens during typing
- the value needs to stay in sync with other state
Use uncontrolled inputs when:
- the field is simple
- the value only matters at submit time
- you want to minimise form state code
Both Patterns Exist Because Forms are Not All the Same
That is the real lesson.
Some form interactions are dynamic, reactive, and closely tied to the rest of the component tree. Controlled components shine there.
Some are modest and self‑contained. Uncontrolled inputs handle those perfectly well without forcing the form into a heavier architecture than it needs.
Once you stop treating the distinction as a purity test, the choice becomes much easier. Controlled components give React more authority. Uncontrolled components leave more work with the browser. Good React code simply chooses the balance that fits the form in front of it.