React Hook Form Zod Typed Resolver

Wire React Hook Form to a Zod resolver so field rules live in one schema, errors arrive per-field, and values stay typed through submit.

TypescriptReactForm Validation

Code

snippet
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const SignupSchema = z.object({
  email: z.string().email('enter a valid email'),
  password: z
    .string()
    .min(8, 'at least 8 characters')
    .regex(/[A-Z]/, 'needs an uppercase letter')
    .regex(/\d/, 'needs a digit'),
  confirm: z.string(),
}).refine((data) => data.password === data.confirm, {
  message: 'passwords do not match',
  path: ['confirm'],
});

type SignupValues = z.infer<typeof SignupSchema>;

function SignupForm({ onDone }: { onDone: (values: SignupValues) => void }) {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<SignupValues>({
    resolver: zodResolver(SignupSchema),
    defaultValues: { email: '', password: '', confirm: '' },
  });

  return (
    <form onSubmit={handleSubmit(onDone)}>
      <input {...register('email')} placeholder="email" />
      {errors.email && <p role="alert">{errors.email.message}</p>}

      <input {...register('password')} type="password" placeholder="password" />
      {errors.password && <p role="alert">{errors.password.message}</p>}

      <input {...register('confirm')} type="password" placeholder="confirm" />
      {errors.confirm && <p role="alert">{errors.confirm.message}</p>}

      <button type="submit" disabled={isSubmitting}>Sign up</button>
    </form>
  );
}