Contents
In brief
The classic form stack — Pydantic on the backend, Zod on the frontend, manual useState and useTransition. The CitizenApp author pairs React 19 Form Actions with FastAPI: validation rules live once on the server; the client receives structured errors via useActionState.
What happened
The "old way" spreads form logic across five places: Pydantic schema, Zod duplicate, REST endpoint, manual error messages, submission callbacks. A new validation rule touches at least three files; when the client passes but the server rejects, users see a spinner and a generic error.
The pattern: the form posts to a server action ('use server'), the action calls FastAPI, FastAPI validates the body with Pydantic and returns 422 details or success. useActionState yields [state, formAction, isPending] — loading and field errors without extra hooks.
The server schema is the single source of truth. TypeScript types can be generated from Pydantic (datamodel-code-generator). JSX filters errors by field and renders them beside inputs.
Why it matters
Duplicated validation is a top source of full-stack bugs. Server checks are mandatory (clients are bypassable), but client-side "UX validation" often diverges from the backend. Centralizing on Pydantic removes drift.
A server action bridges FastAPI without exposing it to the browser — sensitive operations stay server-side. isPending disables the submit button without useState. The author estimates roughly half the form code.
Gotcha: FastAPI 422 responses use a detail array with loc (tuple — field name at index 1) and msg. Map once to { field, message }.
In practice
- Define Pydantic request/response models with
errors: list[ValidationError]. - Server action reads
FormData, POSTs JSON to FastAPI, maps 422 to your format. - Wire
useActionState(action, null)— skip duplicate loading state. - Render errors via
state.errors.filter(e => e.field === 'email'). - Generate TS types from Python schemas — do not copy by hand.
- Custom messages via
Field(description=...)in Pydantic.
Takeaway
React 19 Form Actions plus FastAPI escape the "two schemas, two message sets" trap. Not a replacement for instant client UX hints, but a solid submission foundation. Step-by-step code is in the original on Dev.to.