React da formalar: React Hook Form va Zod orqali validatsiya
Amaliyot3 daqInnovialabs tahririyati
Qisqacha
React Hook Form maydonlar va yuborishni ortiqcha qayta renderlarsiz boshqaradi, Zod sxemasi esa validatsiya qoidalarini bir marta tavsiflaydi — xuddi shu sxemadan serverda ham foydalanish mumkin.
Nega aynan shu juftlik
React Hook Form maydon qiymatlarini React holatidan tashqarida saqlaydi va har bir tugma bosilishida butun formani qayta chizmaydi. Zod ma’lumotlarni sxema bilan tavsiflaydi: tiplar, cheklovlar va xato matnlari bir joyda. Ularni @hookform/resolvers paketi bog‘laydi.
Asosiy foyda — klient va serverda bitta sxema. Qoidalar bir-biridan farq qilmaydi, formaning TypeScript tipi esa sxemadan avtomatik chiqariladi.
npm install react-hook-form zod @hookform/resolvers
1-qadam. Tushunarli xabarlarga ega sxema
Ariza formasini yig‘amiz: ism, O‘zbekiston formatidagi telefon va izoh.
// lib/lead-schema.ts
import { z } from 'zod';
const UZ_PHONE = /^\+998\d{9}$/;
export const leadSchema = z.object({
name: z.string().trim().min(2, 'Ismingizni kiriting — kamida 2 ta belgi'),
phone: z
.string()
.refine(
(v) => UZ_PHONE.test(v.replace(/[\s()-]/g, '')),
'Raqam formati: +998 90 123 45 67',
),
message: z.string().max(1000, '1000 ta belgidan oshmasin').optional(),
});
export type Lead = z.infer<typeof leadSchema>;
Bu yerda muhim jihatlar:
- foydalanuvchi tilidagi xabarlar ikkinchi argument sifatida beriladi;
- telefon bo‘sh joylar, qavslar va defislar olib tashlangandan keyin tekshiriladi, shuning uchun
+998 (90) 123-45-67ham o‘tadi; - O‘zbekiston raqami —
+998va yana 9 ta raqam (operator kodi va abonent raqami).
2-qadam. Maydonlarni ro‘yxatdan o‘tkazish
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { leadSchema, type Lead } from '@/lib/lead-schema';
export function LeadForm() {
const {
register,
handleSubmit,
setError,
reset,
formState: { errors, isSubmitting },
} = useForm<Lead>({ resolver: zodResolver(leadSchema) });
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register('name')} placeholder="Ism" />
{errors.name && <p role="alert">{errors.name.message}</p>}
<input {...register('phone')} type="tel" placeholder="+998 90 123 45 67" />
{errors.phone && <p role="alert">{errors.phone.message}</p>}
<textarea {...register('message')} />
{errors.message && <p role="alert">{errors.message.message}</p>}
{errors.root?.server && <p role="alert">{errors.root.server.message}</p>}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Yuborilmoqda…' : 'Yuborish'}
</button>
</form>
);
}
register maydonni formaga ulaydi, errors sxemadagi matnlarni saqlaydi, isSubmitting esa ikki marta yuborilmasligi uchun tugmani bloklaydi. noValidate atributi brauzerning o‘rnatilgan maslahatlarini o‘chiradi, shunda ikki xil xato ko‘rinmaydi.
3-qadam. Asinxron yuborish va server xatolari
onSubmit funksiyasini komponent ichida e’lon qiling — unga setError va reset kerak:
async function onSubmit(data: Lead) {
const res = await fetch('/api/lead', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) {
const body = await res.json().catch(() => null);
const fieldErrors: Record<string, string> = body?.fieldErrors ?? {};
const entries = Object.entries(fieldErrors);
if (entries.length) {
for (const [field, message] of entries) {
setError(field as keyof Lead, { message });
}
} else {
setError('root.server', { message: 'Yuborib bo‘lmadi. Qaytadan urinib ko‘ring.' });
}
return;
}
reset();
}
handleSubmit onSubmit ni faqat klient validatsiyasi o‘tganda chaqiradi. Server xatolari setError orqali maydonlarga taqsimlanadi, umumiy xato esa root.server ga tushadi.
4-qadam. Serverda xuddi shu sxema
Faqat klient tekshiruviga ishonib bo‘lmaydi: so‘rovni formani chetlab o‘tib yuborish mumkin. Next.js dagi Route Handler misoli:
// app/api/lead/route.ts
import { leadSchema } from '@/lib/lead-schema';
export async function POST(req: Request) {
const result = leadSchema.safeParse(await req.json().catch(() => null));
if (!result.success) {
const fieldErrors: Record<string, string> = {};
for (const issue of result.error.issues) {
const key = String(issue.path[0] ?? 'form');
fieldErrors[key] ??= issue.message;
}
return Response.json({ fieldErrors }, { status: 400 });
}
// result.data tekshirilgan va tiplangan — arizani saqlaymiz
return Response.json({ ok: true });
}
Sxema umumiy faylda turadi, shuning uchun qoidani o‘zgartirish darhol brauzerda ham, serverda ham amal qiladi.
Keng tarqalgan xatolar
- Ma’lumotlarni faqat klientda tekshirish.
- Har bir maydon qiymatini React Hook Form bilan parallel ravishda
useStateda saqlash. - Qoidalarni ikki marta yozish:
register({ required })da va sxemada. - Yuborish vaqtida tugmani bloklashni unutish.
- O‘z matnlaringiz o‘rniga Zod ning standart inglizcha xabarlarini ko‘rsatish.