Forms & Validation

Forms are where most real user interaction happens — and where frontends get surprisingly complex. State, validation, accessibility, async checks, and error UX all collide in one component.

Two principles cut through that complexity: validate against a schema you can reuse on the client and server, and make errors accessible so every user knows what went wrong and how to fix it.

TL;DR

Quick Example

React Hook Form with a Zod schema — one source of truth for shape and messages:

Core Concepts

Controlled vs uncontrolled

Controlled inputs store every keystroke in React state (re-render per change); uncontrolled inputs let the DOM hold the value and read it on submit. React Hook Form is uncontrolled-first, which minimizes re-renders.

Validation layers

Timing

Validate on blur (when leaving a field) and on submit (everything). Validate on change only for critical fields, and debounce async checks (e.g. "username taken").

Best Practices

Make errors accessible

Link errors to inputs, announce them with role="alert", indicate required fields, and move focus to the first error on submit. See Accessibility.

Share schemas with the backend

A Zod schema validates the form and the API request, so the rules can't drift. See API Security for server-side validation.

Comparison

Common Mistakes

Validating on every keystroke

Trusting client validation for security

FAQ

React Hook Form or Formik?

React Hook Form for most new projects — it's uncontrolled-first, so it minimizes re-renders and ships a smaller bundle. Formik is fine for simple forms or existing codebases, but it re-renders more and is less actively favored now.

When should I validate — on change, blur, or submit?

On blur and on submit by default. On-change validation while someone is mid-typing is noisy and frustrating; reserve it for high-stakes fields (like password confirmation) and debounce anything that hits the network.

Do I still need server-side validation if the client validates?

Always. Client validation is purely UX and is trivially bypassed (curl, devtools). The server is the security boundary, so it must re-validate every input. A shared schema lets you do both without duplication.

What's schema validation and why use it?

A schema (Zod, Yup) declares the shape and rules of your data once. You derive both the form validation and the TypeScript types from it, and reuse it server-side — eliminating drift between what the form accepts and what the API accepts.

Related Topics

References