Forms
Controlled vs uncontrolled inputs, multiple fields, checkboxes/selects, submission, and when to reach for RHF.
🧑🏫 Sabse pehle — simple mein samjho#
Controlled input matlab React state hi boss hai — asli data state mein rehta, input toh bas usko dikhata hai. value={text} state se aata hai, aur onChange har keystroke pe state update karta hai. Jaise tumhari diary tumhare haath mein — pen (input) sirf likhne ka zariya, likha hua diary (state) mein safe. Single source of truth = ek hi jagah sach.
function Name() {
const [text, setText] = useState("");
return (
<input
value={text} // state dikhata hai
onChange={e => setText(e.target.value)} // typing → state update
/>
);
}
Yaad rakho: input sirf dikhata hai, asli data hamesha state mein rehta hai.
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} />
</>
);
}
Real-world example: signup form (Flipkart/IRCTC style)#
A realistic account-creation form has several fields — name, email, password — all wired to a single handleChange, plus basic validation before submitting.
function SignupForm() {
const [form, setForm] = useState({ name: "", email: "", password: "" });
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value })); // one handler, every field
}
function handleSubmit(e) {
e.preventDefault();
const nextErrors = {};
if (!form.name.trim()) nextErrors.name = "Name is required";
if (!/^\S+@\S+\.\S+$/.test(form.email)) nextErrors.email = "Enter a valid email";
if (form.password.length < 8) nextErrors.password = "Min 8 characters";
setErrors(nextErrors);
if (Object.keys(nextErrors).length === 0) {
createAccount(form); // only submit when clean
}
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={form.name} onChange={handleChange} placeholder="Full name" />
{errors.name && <p className="error">{errors.name}</p>}
<input name="email" value={form.email} onChange={handleChange} placeholder="Email" />
{errors.email && <p className="error">{errors.email}</p>}
<input name="password" type="password" value={form.password} onChange={handleChange} placeholder="Password" />
{errors.password && <p className="error">{errors.password}</p>}
<button type="submit">Create account</button>
</form>
);
}
🟢 One handleChange covers every field because name on each <input> tells it which key of form to update — the same computed-key pattern from the section above, scaled to a real signup screen.
The controlled-input render loop#
flowchart LR
S["State: form.email"] --> V["value prop on the input"]
V --> IN["Input renders with that value"]
IN --> T["User types a character"]
T --> OC["onChange fires with e.target.value"]
OC --> SS["setForm updates state"]
SS --> RR["Component re-renders"]
RR --> S
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.