Intermediate⏱️ 9 min📘 Topic 6 of 13

📝 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.