March 8, 2025
Build fully validated, type-safe forms in React using React Hook Form for state management and Zod for schema validation.

React Hook Form handles form state without re-renders. Zod defines the validation schema. The zodResolver bridges the two so validation runs automatically on submit and field blur.
Together they give you:
onChange handlersimport { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
const schema = z.object({
email: z.string().email("Enter a valid email"),
password: z.string().min(8, "At least 8 characters"),
});
type FormValues = z.infer<typeof schema>;
const SignInForm = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({ resolver: zodResolver(schema) });
const onSubmit = (values: FormValues) => {
console.log(values);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
<div>
<Input {...register("email")} placeholder="Email" />
{errors.email && (
<p className="text-destructive mt-1 text-sm">{errors.email.message}</p>
)}
</div>
<div>
<Input type="password" {...register("password")} placeholder="Password" />
{errors.password && (
<p className="text-destructive mt-1 text-sm">{errors.password.message}</p>
)}
</div>
<Button type="submit">Sign in</Button>
</form>
);
};Wire the form directly to a tRPC mutation for end-to-end type safety:
const trpc = useTRPC();
const createPost = useMutation(trpc.posts.create.mutationOptions({
onSuccess: () => toast.success("Post created"),
onError: () => toast.error("Something went wrong"),
}));
const onSubmit = (values: FormValues) => createPost.mutate(values);
// In JSX:
<Button type="submit" disabled={createPost.isPending}>
{createPost.isPending ? <Spinner /> : "Create"}
</Button>Avoid repeating error message markup by wrapping label, input, and error into a Field:
import { Field } from "@/components/ui/field";
<Field label="Email" error={errors.email?.message}>
<Input {...register("email")} />
</Field>z.coerce.number() for numeric inputs since HTML inputs always return stringsYour laboratory instruments should serve you, not the other way around. We're happy to help you.