Bạn gặp yêu cầu: Form "Tạo hóa đơn", user có thể bấm nút + Thêm sản phẩm vô hạn lần. Mỗi sản phẩm có Tên, Số lượng, Đơn giá.
Nếu dùng useState mảng arr[] và tự handle onChange index i -> Cực khó và bug đầy rẫy.
React Hook Form tặng bạn vũ khí hạng nặng: useFieldArray.
1. Setup useFieldArray
tsx:
import { useForm, useFieldArray } from "react-hook-form";
function InvoiceForm() {
const { control, register } = useForm({
defaultValues: {
items: [{ name: "Item 1", quantity: 1, price: 0 }] // Giá trị khởi tạo
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "items" // Tên field trong object data
});
return (
<ul>
{fields.map((item, index) => (
<li key={item.id}> {/* QUAN TRỌNG: Dùng item.id làm key, không dùng index */}
{/* Register dạng nested: items[0].name */}
<input {...register(`items.${index}.name`)} />
<input type="number" {...register(`items.${index}.quantity`)} />
<button type="button" onClick={() => remove(index)}>Xóa</button>
</li>
))}
<button
type="button"
onClick={() => append({ name: "", quantity: 1, price: 0 })}
>
+ Thêm sản phẩm
</button>
</ul>
);
}2. Tại sao useFieldArray lại tốt hơn?
- Unique ID: RHF tự động sinh
id(UUID) cho mỗi item. Dùng cái này làmkeygiúp React không render lại toàn bộ list khi xóa 1 item ở giữa. - Performance: Nó không re-render toàn bộ form khi bạn append item mới.
- Drag & Drop: Hỗ trợ hàm
move(from, to)vàswap(A, B)cực tiện để làm tính năng sắp xếp thứ tự.
3. Nested Validation với Zod
Schema Zod cho array cũng rất đơn giản:
ts:
const InvoiceSchema = z.object({
items: z.array(
z.object({
name: z.string().min(1),
quantity: z.number().min(1),
})
).min(1, "Phải có ít nhất 1 sản phẩm") // Validate độ dài mảng
});Lỗi sẽ nằm ở errors.items[index].name.
Kết luận
Với useFieldArray, việc xử lý các form danh sách động (Dynamic List) trở nên dễ như ăn kẹo. Mọi logic thêm/sửa/xóa/đổi chỗ đều đã được RHF lo liệu.