What Is the Difference Between Controlled and Uncontrolled Components?
Advertisement
🧩 Scenario
Architecture Walkthrough
Controlled Components
A controlled component is a form element whose value is set from React state and updated via an event handler. The cycle is: React state holds the value, renders it as the element's value prop, onChange fires when the user types, the handler calls the state setter, React re-renders with the new state, and the input displays the updated value. React is the single source of truth.
This approach has a performance cost: every keystroke triggers a state update and re-render of the component (and its children, unless memoized). For most forms this is imperceptible, but in very large forms with many concurrent fields it can become a bottleneck.
The main advantages of controlled components: the value is always available in state (no need to query the DOM), live validation and previews work naturally, dependent fields update in sync, and programmatic reset is trivial (just set the state).
Uncontrolled Components
An uncontrolled component lets the DOM own the value. Instead of value, you use defaultValue to set the initial value (the DOM updates freely after that). To read the value, you attach a ref and access ref.current.value on demand (typically in a submit handler). There is no re-render on every keystroke.
This approach is simpler for submit-only forms where you do not need the value during typing. It is also the strategy react-hook-form uses by default, which is why it is significantly faster than Formik for large forms. The cost is that the value is not in React state: live validation, dependent field updates, and previews require extra work.
File inputs (<input type="file">) must always be uncontrolled. React does not allow setting their value programmatically for security reasons.
Key Code Explained
// Controlled: React owns the value
import { useState } from 'react';
function ControlledForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Values are already in state — no DOM query needed
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email} // React state drives the input
onChange={(e) => setEmail(e.target.value)} // state updates on every keystroke
placeholder="Email"
/>
{/* Live validation: possible because value is in state */}
{email && !email.includes('@') && (
<p className="error">Enter a valid email</p>
)}
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Log In</button>
</form>
);
}
// Uncontrolled: DOM owns the value
import { useRef } from 'react';
function UncontrolledForm() {
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Read from the DOM via refs — only needed on submit
const email = emailRef.current?.value ?? '';
const password = passwordRef.current?.value ?? '';
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
ref={emailRef}
type="email"
defaultValue="" // initial value only — DOM updates freely after this
placeholder="Email"
/>
<input
ref={passwordRef}
type="password"
defaultValue=""
placeholder="Password"
/>
<button type="submit">Log In</button>
</form>
);
}
// Why controlled makes live features easy
function LiveSearchInput() {
const [query, setQuery] = useState('');
const suggestions = useSuggestions(query); // value is available every keystroke
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
{/* suggestions driven by state — trivial with controlled */}
{suggestions.length > 0 && (
<ul>
{suggestions.map((s) => <li key={s.id}>{s.label}</li>)}
</ul>
)}
</div>
);
}
// File input: must always be uncontrolled
function FileUpload() {
const fileRef = useRef<HTMLInputElement>(null);
const handleUpload = () => {
const file = fileRef.current?.files?.[0];
if (file) uploadToServer(file);
};
return (
<div>
{/* No value prop — file inputs are always uncontrolled */}
<input ref={fileRef} type="file" accept="image/*" />
<button onClick={handleUpload}>Upload</button>
</div>
);
}
The LiveSearchInput example illustrates why controlled inputs are the default choice: the value is available in state on every keystroke, making the suggestions hook trivial to implement. With an uncontrolled input you would need a ref and an onChange to read the value into a local variable, at which point you have essentially rebuilt a controlled input without React's re-render guarantee.
Tradeoffs
| Aspect | Controlled | Uncontrolled |
|---|---|---|
| Value source | React state | DOM |
| Re-renders | Every keystroke | Only on submit/explicit read |
| Live validation | Trivial (value in state) | Requires extra plumbing |
| Reset | Set state to initial value | Call reset() on the form or ref |
| Library adoption | Formik (controlled) | react-hook-form (uncontrolled) |
| File inputs | Not supported | Required |
What Interviewers Actually Check
- Whether you can implement both patterns correctly
- Whether you know the re-render cost difference
- Whether you can choose the right pattern for the use case
- Whether you know file inputs must be uncontrolled
- Whether you know why react-hook-form is faster (uncontrolled by default)
Follow-Up Questions
- How does
react-hook-form'sregister()API use uncontrolled inputs under the hood, and how doesControllerwrap third-party controlled components? - How would you reset an uncontrolled form to its default values after a successful submission?
- How does React's
useId()hook help with associating labels and inputs correctly in form components? - What is the performance cost of a controlled input in a 100-field form and how would you measure it with React DevTools Profiler?
- How does
Formik'ssetFieldValuediffer from a plainsetStatefor controlled inputs?
Common Candidate Mistakes
- Setting
valuewithoutonChangeand being surprised the input is read-only (React locked it) - Mixing
valueanddefaultValueon the same input, which React warns about because they conflict - Not knowing that re-renders on every keystroke is a real cost in large controlled forms
- Trying to use a controlled file input (
<input type="file" value={...} />) and not knowing React explicitly disallows it - Reading
ref.current.valueinside a render function where it reflects the DOM state, not React state, causing subtle synchronization bugs
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you implement a controlled input with
valuefrom state andonChangeupdating state? - Can you implement an uncontrolled input with
defaultValueand arefto read the value on submit? - Can you explain the re-render difference between the two and when it matters?
- Can you choose the right approach for a given form scenario?
- Can you explain why file inputs must always be uncontrolled?
Summary
A controlled component stores its value in React state. The input renders the state value and calls a state setter on every onChange. React is the single source of truth, the value is always available in state, and live features like validation, dependent fields, and previews are trivial. The cost is a re-render on every keystroke.
An uncontrolled component lets the DOM own its value. The initial value is set with defaultValue and the DOM updates freely as the user types with no React re-renders. To read the value, attach a ref and access ref.current.value when needed (typically on submit). This is how react-hook-form achieves its performance advantage over Formik.
Default to controlled components. They are more predictable and easier to work with in any interactive scenario. Uncontrolled components are appropriate for submit-only forms with many fields where the keystroke re-render cost is measurable, or when integrating with third-party DOM libraries. File inputs must always be uncontrolled because React does not support setting their value programmatically.
Which should I use by default — controlled or uncontrolled?
Default to controlled components. They make form state predictable, easy to validate, and easy to use in live previews or dependent fields. Uncontrolled components are appropriate for simple forms where you only need the values on submit and re-renders on every keystroke are undesirable.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement