Controlled forms, validation and submit handling
Bind inputs to state with one change handler, validate before submit, and avoid the warnings that reveal form mistakes.
Almost every React coding screen asks you to build a form: a login box, a search bar, a sign-up page. The interviewer is checking whether you understand controlled inputs, whether you stop the browser's default submit, and whether your validation logic is clear. This lesson gives you a pattern you can type from memory.
One state object, one change handler
A controlled input gets its value from state and reports changes through onChange. With several fields, keep them in one object and use the input's name attribute to decide which key to update.
import { useState } from "react";
function SignupForm() {
const [form, setForm] = useState({ email: "", password: "" });
const [errors, setErrors] = useState({});
function handleChange(e) {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
}
function handleSubmit(e) {
e.preventDefault();
const next = {};
if (!form.email.includes("@")) next.email = "Enter a valid email.";
if (form.password.length < 8) next.password = "Use at least 8 characters.";
setErrors(next);
if (Object.keys(next).length === 0) {
console.log("Submitting", form);
}
}
return (
<form onSubmit={handleSubmit} noValidate>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email"
value={form.email} onChange={handleChange} />
{errors.email && <p role="alert">{errors.email}</p>}
<label htmlFor="password">Password</label>
<input id="password" name="password" type="password"
value={form.password} onChange={handleChange} />
{errors.password && <p role="alert">{errors.password}</p>}
<button type="submit">Create account</button>
</form>
);
}The updater form setForm((prev) => ...) matters when two changes land close together. Spreading prev keeps the other fields; [name] writes only the one that changed.
Checkboxes, selects and textareas
A checkbox reports e.target.checked, not value, so give it its own handler or branch on e.target.type. A <select> takes value on the select element itself, not a selected attribute on an option. A <textarea> also uses value rather than children. Here is a second worked example for a checkbox inside the same handler:
function handleChange(e) {
const { name, type, value, checked } = e.target;
setForm((prev) => ({ ...prev, [name]: type === "checkbox" ? checked : value }));
}Common mistakes
- Forgetting
e.preventDefault(), so the page reloads and state is lost. - Passing
valuewithoutonChange. React warns and the field becomes read-only. - Starting state as
undefinedand later setting a string. React warns about switching from uncontrolled to controlled. Initialise with"". - Using
forandclassin JSX instead ofhtmlForandclassName. - Validating on every keystroke from the first character, which shows errors before the user has finished typing. Validate on blur or submit.
Practice habit
Build the form above from a blank file without looking, then add one field type you have not used (a select, a radio group, a number input). Doing this three times in a week makes the pattern automatic, which is exactly what a timed screen rewards.
More in Web and app