A resolver is the function React Hook Form 351,018 calls to validate the whole form. zodResolver(schema) runs safeParse and files each issue in formState.errors under its path. @hookform/resolvers 5.9.1 accepts Zod 3 44,027 and Zod 4 schemas and infers the form's types from the schema, so useForm needs no type argument:
const { control, handleSubmit, setError, formState } = useForm({
resolver: zodResolver(checkoutSchema),
mode: 'onTouched', // first check on blur, then on every change
defaultValues: { name: user ?? '', email: '', delivery: 'ship' as const,
address: '', postcode: '' },
});
const { errors, isSubmitting } = formState;
const delivery = useController({ control, name: 'delivery' }).field;Give every field a default (reset() returns to them). Without as const, TypeScript widens delivery to string and npx tsc --noEmit rejects the resolver line: "Type 'string' is not assignable to type '"ship" | "pickup"'". With a resolver, skip per-field rules: the schema is the single source of truth, and it can run again on the server, where validation actually protects your data.