Lesson 5 of 9 · 20m
Forms and Validation That Don't Fight the User
Controlled vs uncontrolled inputs, schema validation, accessible errors, and the submission states most forms get wrong.
Forms are where UX and state management collide. Do it with forty useState calls and you'll ship a form that clears itself, double-submits, and reports errors the screen reader never announces.
Controlled or uncontrolled — pick deliberately
// controlled: React owns the value — for live validation, formatters, dependent fields
<input value={email} onChange={e => setEmail(e.target.value)} />
// uncontrolled: DOM owns the value — for file inputs, perf-critical fields, "submit and done"
<input name="email" defaultValue="" />Controlled inputs are worth it when you need the value during editing. For a plain login form, uncontrolled + FormData in the submit handler is less code and faster. A mixed form (controlled password with live strength meter, uncontrolled everything else) is legitimate — just don't accidentally flip an input from one to the other by passing undefined where you meant "".
Validate on blur, confirm on submit
Validating on every keystroke screams "invalid email" while the user is still typing. The pattern that doesn't annoy: validate the field when it loses focus, validate everything on submit, and clear the error as soon as the input becomes valid. Schema libraries (zod) keep rules in one place so client and server can't drift:
const schema = z.object({
email: z.string().email(),
password: z.string().min(12),
});Submission is a state machine
idle → submitting → success | error. While submitting: disable the button, show progress, and guard against double-click. On error: keep the data, announce the error accessibly (aria-live region or aria-invalid + aria-describedby on the field), and move focus somewhere sensible.
Field notes
<form>with a real submit button andonSubmiton the form — neveronClickon the button alone. Enter-to-submit and screen readers depend on it.- Libraries like react-hook-form exist because controlled-at-scale gets expensive; reach for them when a form grows past ~6 fields.
- Autocomplete attributes (
autocomplete="email",name="email") are free UX and accessibility wins. Use them.