25

The recurring pain: the zod schema, the TypeScript type, and the form field names would drift apart, so you'd validate a field the form didn't have, or submit a shape the API rejected. Three sources of truth for one form.

I wrote a prompt that makes zod the single source of truth, infers the TS type from it, and wires react-hook-form so a typo in a field name is a compile error. The submit handler receives a fully parsed, typed value. No more drift.

Bonus question: how are you all handling async field validation (like username-taken) inside this without breaking the typing?

THE PROMPT
Build a typed form in React using react-hook-form and zod, with ONE source of truth for the shape.

RULES:
- The zod schema is the single source of truth. Derive the TS type with `z.infer`. Do NOT hand-write a separate interface for the form values.
- Wire zodResolver so validation and types come from the same schema.
- Register fields with a typed name so a misspelled field name is a COMPILE error, not a runtime surprise. No stringly-typed field names that TS can't check.
- The onSubmit handler receives the parsed, typed output of the schema (post-transform), not the raw input.
- Show inline, accessible error messages: each input is aria-describedby its error, invalid inputs get aria-invalid, and focus moves to the first error on failed submit.
- Handle: required fields, cross-field validation (e.g. confirmPassword matches password via .refine), and a submit that can itself fail (server error) mapped back onto the form.

STRICT TS, no any, no casting the resolver. Deliver the schema, the form component, and show that renaming a field in the schema without updating the JSX produces a type error.
one source of truth for form shape is one of those things you don't appreciate until you've maintained a form with three. the compile-error-on-typo bit alone sells it.greenfield_gus 3 months ago
add a comment

1 Answer

13

The a11y bits being in the base prompt is exactly right, forms are where accessibility quietly dies. For your async question: keep zod as the source of truth for shape/sync rules, and handle username-taken as an async validate on that single field via react-hook-form's validate option (or setError after the API call), not inside the zod schema. Mixing async into the schema is where the typing gets ugly. The type stays clean because the async check only ever sets an error, it doesn't change the value shape.

THE PROMPT
For async checks (e.g. username-taken), don't put them in zod. Use rhf's per-field async validate or setError after the request, so the schema stays synchronous and z.infer stays clean.

Your Answer