Chuyển tới nội dung chính

Form Validation với Zod + React Hook Form

Setup

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

Ví dụ đầy đủ

1. Định nghĩa Schema (Zod)

// src/schema/product.schema.ts
import { z } from "zod";

export const productSchema = z.object({
name: z.string().min(1, "Tên sản phẩm không được để trống"),
price: z.number().min(0, "Giá phải >= 0"),
description: z.string().optional(),
categoryId: z.string().min(1, "Vui lòng chọn danh mục"),
isActive: z.boolean().default(true),
});

export type ProductFormValues = z.infer<typeof productSchema>;

2. Sử dụng trong Form

import { productSchema, type ProductFormValues } from "@/schema/product.schema";

function ProductForm() {
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<ProductFormValues>({
resolver: zodResolver(productSchema),
defaultValues: {
name: "",
price: 0,
isActive: true,
},
});

const [createProduct] = useCreateProductMutation();

const onSubmit = async (data: ProductFormValues) => {
try {
await createProduct(data).unwrap();
toastUtils.success("Tạo sản phẩm thành công");
} catch (err) {
const apiError = getApiError(err);
if (apiError?.fieldErrors) {
setFormErrors(setError, apiError.fieldErrors);
}
toastUtils.error(getApiErrorMessage(err));
}
};

return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input label="Tên sản phẩm" {...register("name")} error={errors.name?.message} />
<Input
label="Giá"
type="number"
{...register("price", { valueAsNumber: true })}
error={errors.price?.message}
/>
<Button type="submit" isLoading={isSubmitting}>
Tạo
</Button>
</form>
);
}

Quy tắc

  • Luôn dùng Zod cho validation — không dùng Yup, class-validator, hoặc if/else
  • Schema file đặt trong src/schema/ hoặc cạnh page sử dụng
  • Type form values luôn z.infer<typeof schema> — không định nghĩa riêng
  • Server errors map vào form qua setFormErrors(setError, fieldErrors)