📝 Forms in React — Controlled vs Uncontrolled Components
Build React forms the right way. Controlled vs uncontrolled components, useRef for inputs, form submission, and validation patterns — with examples and interview Q&A.
React handles forms in two styles:
- Controlled — React state is the source of truth. Every keystroke updates state, value comes from state.
- Uncontrolled — The DOM owns the value. You read it via a ref when needed.
🎯 Controlled (the default)
const [name, setName] = useState('');
<input
value={name}
onChange={e => setName(e.target.value)}
/>Pros: live validation, conditional formatting, easy to disable submit until valid.
🟢 Uncontrolled (occasionally useful)
const nameRef = useRef(null);
<input defaultValue="" ref={nameRef} />
<button onClick={() => alert(nameRef.current.value)}>Read</button>Pros: less code, fewer re-renders, easy file inputs.
📤 Submitting
function Form() {
const [email, setEmail] = useState('');
function onSubmit(e) {
e.preventDefault();
fetch('/api/subscribe', { method: 'POST', body: JSON.stringify({ email }) });
}
return (
<form onSubmit={onSubmit}>
<input value={email} onChange={e => setEmail(e.target.value)} />
<button>Subscribe</button>
</form>
);
}💡 File inputs are always uncontrolled
You can't set value on a file input — read it via ref.
💻 Code Examples
Simple controlled form
function Login() {
const [email, setEmail] = useState('');
const [pass, setPass] = useState('');
return (
<form onSubmit={e => { e.preventDefault(); login(email, pass); }}>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={pass} onChange={e => setPass(e.target.value)} />
<button disabled={!email || !pass}>Sign in</button>
</form>
);
}Output:
Submit disabled until both fields are non-empty.
Uncontrolled file input
function Upload() {
const fileRef = useRef(null);
function onSubmit(e) {
e.preventDefault();
const file = fileRef.current.files[0];
upload(file);
}
return (
<form onSubmit={onSubmit}>
<input type="file" ref={fileRef} />
<button>Upload</button>
</form>
);
}Output:
File only read when form submits.
⚠️ Common Mistakes
- Setting `value` without `onChange` — React warns and the input becomes read-only.
- Forgetting `e.preventDefault()` in onSubmit — page reloads.
- Re-rendering the whole form on every keystroke of a huge form — split into smaller components or use react-hook-form.
- Trying to make file inputs controlled — browsers don't allow setting their value.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What's the difference between controlled and uncontrolled components?Beginner
Controlled = React state holds the value, every change goes through setState. Uncontrolled = the DOM holds the value, you read it via a ref. Controlled is the recommended default; uncontrolled is occasionally useful for file inputs or perf-sensitive cases.
Q2.How do you read an uncontrolled input's value?Intermediate
Attach a `ref` to it: `const ref = useRef(null); <input ref={ref} />`. Read via `ref.current.value` when needed (typically on submit).
Q3.Why might you choose uncontrolled?Intermediate
Less code, fewer re-renders, file inputs (which can't be controlled), or one-shot reads where you don't need React aware of every keystroke.
Q4.What's `defaultValue` and how is it different from `value`?Intermediate
`defaultValue` sets the initial value of an uncontrolled input; the user can edit freely. `value` makes the input controlled — React owns it, you must update via onChange.
Q5.How do you handle large forms without re-rendering the whole tree on each keystroke?Advanced
Split each field into its own component, use uncontrolled inputs + refs, or use a form library like react-hook-form which uses subscriptions to avoid root-level re-renders.
🧠 Quick Summary
- Controlled: state is the truth, onChange + value.
- Uncontrolled: DOM is the truth, read via useRef.
- Always preventDefault on form submit.
- File inputs are always uncontrolled.
- Big forms? Split components or use react-hook-form.