Forms & Validation
Controlled vs uncontrolled, and validating the same rules on both sides of the wire.
35 minDifficulty 2/5react · formsAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A signup form validates the email format on the client, the user submits, and the server rejects it anyway because the client and server disagree on what counts as valid — or worse, the server trusts the client's validation entirely and a request crafted by hand, bypassing the browser altogether, inserts garbage straight into the database. Client-side validation is for user experience; server-side validation is the actual security boundary.
The mental model
A controlled input's value lives in React state, and every keystroke updates that state via `onChange` — React is the single source of truth, and the input just reflects it. An uncontrolled input keeps its value in the DOM itself, the way a plain HTML form works, and you read it out only when needed (usually via a ref), rather than tracking every keystroke in state.
How it works
Controlled inputs enable validation and formatting on every keystroke
Because a controlled input's value is React state, you can inspect and transform it on every change — showing a validation error the moment a field becomes invalid, formatting a phone number as it's typed, or disabling a submit button based on the current values. This immediate feedback is the main reason to reach for controlled inputs over uncontrolled ones.
Client-side validation is a UX feature, not a security boundary
Anyone can bypass client-side JavaScript entirely — disable it, or send a crafted HTTP request directly to the API with a tool like curl, skipping the browser altogether. Client-side validation exists to give immediate, friendly feedback; the server must independently re-validate every field, because it's the server's validation that actually decides what enters the database.
Validate on blur or submit, not on every keystroke, for a good experience
Showing 'this field is required' the instant a user clicks into an empty field, before they've had a chance to type anything, feels hostile. Validating on blur (when they leave the field) or on submit, rather than on every keystroke while they're still typing, produces feedback that feels helpful rather than nagging — though switching to per-keystroke validation once an error has already been shown, to clear it as soon as it's fixed, is a reasonable refinement.
The same validation rules should live in one place, checked twice
Defining an email format rule once — as a shared schema, say — and running that identical rule on both the client (for instant feedback) and the server (as the actual gate) avoids the two sides silently drifting apart over time, which is exactly the bug where a client accepts something the server later rejects, or vice versa.
The mechanism
A controlled input's onChange handler updates React state on every keystroke, and the input's value prop is set back from that same state — a closed loop where React fully owns the current value. Validation logic reads that state (on blur, on submit, or continuously) and derives an error message, which the component conditionally renders next to the field. On submit, the same validation rules run again server-side before the data is trusted or stored.
What people get wrong
- If the client validates a field, the server doesn't need to check it again.
- Client-side JavaScript can always be bypassed — via disabled JS, a crafted request, or a modified browser — so any validation the server doesn't independently repeat is a validation the server can't actually rely on. This is a specific, common instance of a broader security principle covered in `input-validation`: never trust the client, because the client is not a boundary you control.
- Controlled inputs are always the right choice for every form field.
- For very large forms or performance-sensitive cases, tracking every keystroke of every field in React state can cause noticeable re-render overhead — an uncontrolled approach reading values only on submit (or a library optimized for this) can perform better at scale. Defaulting to fully controlled everything without considering the form's size or update frequency can introduce avoidable performance issues in genuinely large forms.
- Validating on every keystroke gives the best user experience.
- Showing errors before a user has finished typing (or even started) typically feels premature and annoying — validating on blur or submit, then switching to live validation only after an error has already appeared, is generally better received. Aggressive real-time validation is a common, well-intentioned UX mistake that makes forms feel like they're nagging rather than helping.
When not to use it
- A form has dozens of fields and performance from per-keystroke re-renders becomes noticeable.
- An uncontrolled approach using refs, or a form library designed to minimize re-renders by isolating state updates to individual fields rather than the whole form.
- The validation rule is about business logic that genuinely requires server data, like checking if a username is already taken.
- An async validation that queries the server (debounced, to avoid a request per keystroke) — this can't be checked purely client-side since the answer depends on data only the server has.
Terms
- Controlled input
- — A form input whose value is fully owned by React state, updated via onChange and reflected back via the value prop.
- Uncontrolled input
- — A form input that manages its own value internally in the DOM, read out on demand (typically via a ref) rather than tracked in React state on every change.
- Validate on blur
- — Running validation logic when a field loses focus, rather than on every keystroke — a common middle ground between immediate and submit-only feedback.
- Shared validation schema
- — A single definition of validation rules used by both client and server, avoiding the two sides drifting into disagreement over what counts as valid.
In an interview
Why is client-side form validation not sufficient on its own, even if it's thorough?
- client-side JavaScript can be bypassed entirely — disabled, or the request sent directly to the API without going through the browser form at all
- the server is the actual trust boundary and must independently re-validate every field before storing or acting on the data
Can you recall it?
Why must server-side validation exist even when a form already has thorough client-side validation?