Topics in this subject
React 3 min read Updated 5 Aug 2026

Forms

Controlled vs uncontrolled inputs, multiple fields, checkboxes/selects, submission, and when to reach for RHF.

Controlled inputs 🎯#

State is the single source of truth: the input's value comes from state and every keystroke flows through onChange. React re-renders on each change, so the value in state and on screen are always in sync.

function Name() {
  const [name, setName] = useState("");
  return <input value={name} onChange={e => setName(e.target.value)} />;
}

Uncontrolled inputs#

The DOM holds the value; you read it on demand via a ref and set an initial value with defaultValue (never value). No re-render per keystroke.

function Name() {
  const inputRef = useRef(null);
  function onSubmit(e) {
    e.preventDefault();
    console.log(inputRef.current.value);   // read from the DOM
  }
  return (
    <form onSubmit={onSubmit}>
      <input defaultValue="Ada" ref={inputRef} />
    </form>
  );
}

Controlled vs uncontrolled#

Aspect Controlled Uncontrolled
Source of truth React state The DOM
Value prop value defaultValue
Read value From state ref.current.value
Re-render per keystroke Yes No
Instant validation / formatting Easy 🟢 Awkward
Best for Most forms, dynamic UI File inputs, quick/simple forms, integrating non-React widgets

⚠️ Don't switch a field between controlled and uncontrolled (e.g. value={x} where x starts undefined then becomes a string) — React warns and resets cursor/behavior. Initialize state to "".

Multiple inputs with one handler#

Give each input a name and key state by it. 🟢

function Profile() {
  const [form, setForm] = useState({ email: "", city: "" });
  const onChange = e => {
    const { name, value } = e.target;
    setForm(f => ({ ...f, [name]: value }));   // computed key
  };
  return (
    <>
      <input name="email" value={form.email} onChange={onChange} />
      <input name="city"  value={form.city}  onChange={onChange} />
    </>
  );
}

Checkboxes & selects#

Checkboxes use checked/e.target.checked (not value). A <select multiple> yields multiple selected options.

<input type="checkbox" checked={agree} onChange={e => setAgree(e.target.checked)} />

<select value={city} onChange={e => setCity(e.target.value)}>
  <option value="del">Delhi</option>
  <option value="blr">Bengaluru</option>
</select>

Submission#

Handle onSubmit on the <form> (fires on Enter and button click) and call e.preventDefault() to stop the full-page reload.

<form onSubmit={e => { e.preventDefault(); save(form); }}>…<button>Save</button></form>

When to reach for a library#

For large forms, complex validation, and performance, React Hook Form uses uncontrolled inputs + refs to minimize re-renders (register, handleSubmit), pairing well with schema validators like Zod/Yup. Formik is the older controlled-state alternative.

flowchart TD
  A["User types"] --> B{"Controlled?"}
  B -->|Yes| C["onChange -> setState"] --> D["Re-render, value = state"]
  B -->|No| E["DOM stores value"] --> F["Read via ref on submit"]

Interview Q&A#

Q1. Controlled vs uncontrolled — the one-line difference? Controlled inputs derive their value from React state (value + onChange); uncontrolled inputs keep the value in the DOM and you read it via a ref (defaultValue).

Q2. When would you prefer uncontrolled? File inputs (which are always uncontrolled), simple forms where per-keystroke state is unnecessary, integrating third-party/non-React widgets, and to avoid re-renders in very large forms.

Q3. Why does React warn about switching controlled/uncontrolled? Passing value={undefined} then a string makes React unsure who owns the value, causing lost input state and cursor jumps. Initialize state to a defined value like "".

Q4. How do you handle many inputs cleanly? Give each a name, store an object in state, and use a computed key: setForm(f => ({ ...f, [name]: value })).

Q5. Why is React Hook Form fast? It keeps inputs uncontrolled and subscribes via refs, so typing doesn't trigger component re-renders — only subscribed fields update.