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)