React Hook Form — Zod bilan schema validation
Muammodan boshlaylik
64-darsdagi forma qoidalari JSX bo'ylab tarqalgan:
<input {...register('email', { required: '...', pattern: { value: /.../, message: '...' } })} />
<input {...register('yosh', { valueAsNumber: true, min: { ... }, max: { ... } })} />
<input {...register('parolTakror', { validate: (v) => v === getValues('parol') || '...' })} />
Muammolar:
- Qoidalarni qayta ishlatib bo'lmaydi — ro'yxatdan o'tish va profil tahrirlashda email qoidasi takrorlanadi
- Backend da (Node.js) xuddi shu qoidalarni qaytadan yozish kerak
- TypeScript da forma turi (
type Forma = { email: string; yosh: number }) alohida yoziladi va qoidalar bilan farq qilib qolishi mumkin - Qoidalarni komponentsiz testlab bo'lmaydi
Yechim — barcha qoidalarni bitta joyda, sxema sifatida yozish. Eng mashhur sxema kutubxonasi — Zod.
Zod nima
npm install zod @hookform/resolvers
zod— sxema kutubxonasi@hookform/resolvers— React Hook Form ni Zod (va boshqa kutubxonalar) bilan bog'lovchi
Zod da ma'lumot qanday bo'lishi kerakligini tasvirlaysiz:
import { z } from 'zod';
const foydalanuvchiSchema = z.object({
ism: z.string().min(2),
yosh: z.number().int().min(18),
email: z.string().email(),
});
foydalanuvchiSchema.parse({ ism: 'Ali', yosh: 25, email: 'ali@mail.uz' }); // ✅ ma'lumotni qaytaradi
foydalanuvchiSchema.parse({ ism: 'A', yosh: 15, email: 'ali' }); // ❌ ZodError — 3 ta xato
const natija = foydalanuvchiSchema.safeParse(malumot);
if (!natija.success) console.log(natija.error.issues); // throw siz
Zod React ga bog'liq emas — u istalgan JavaScript kodida ishlaydi: frontend, backend, API javoblarini tekshirish.
[!INFO] Zod ning 4-versiyasida email kabi format tekshiruvlari uchun yuqori darajadagi funksiyalar qo'shildi:
z.email(),z.url(). Eskiz.string().email()sintaksisi ham ishlayveradi. Bu darsdagi misollar ikkala versiyada ishlaydi.
Asosiy turlar va qoidalar
z.string() // matn
.min(2, 'Kamida 2 belgi')
.max(50, "Ko'pi bilan 50 belgi")
.email("Email noto'g'ri")
.url("URL noto'g'ri")
.regex(/^\+998\d{9}$/, 'Format: +998XXXXXXXXX')
.trim() // bo'sh joylarni olib tashlash
z.number() // son
.int('Butun son bo\'lsin')
.positive()
.min(18, '18 yoshdan katta')
.max(100)
z.boolean() // true / false
z.date() // Date
z.enum(['kuryer', 'pochta', 'ozi']) // faqat shu qiymatlardan biri
z.array(z.string()).min(1, 'Kamida bittasini tanlang')
z.object({ ... })
.optional() // undefined bo'lishi mumkin
.nullable() // null bo'lishi mumkin
.default('qiymat') // bo'lmasa — standart
Har bir qoidaning oxirgi argumenti — xato xabari.
React Hook Form bilan ulash
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const royxatSchema = z.object({
ism: z.string().trim().min(2, 'Ism kamida 2 harf'),
email: z.string().email("Email formati noto'g'ri"),
yosh: z.coerce.number().int().min(16, '16 yoshdan katta bo\'lishi kerak'),
shartlar: z.boolean().refine((v) => v === true, { message: 'Shartlarga rozilik bering' }),
});
function Royxat() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm({
resolver: zodResolver(royxatSchema),
defaultValues: { ism: '', email: '', yosh: '', shartlar: false },
mode: 'onTouched',
});
async function onSubmit(data) {
// data — Zod tomonidan tekshirilgan va o'zgartirilgan: yosh — son, ism — trim qilingan
await api.royxat(data);
}
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input {...register('ism')} />
{errors.ism && <p role="alert">{errors.ism.message}</p>}
<input type="email" {...register('email')} />
{errors.email && <p role="alert">{errors.email.message}</p>}
<input type="number" {...register('yosh')} />
{errors.yosh && <p role="alert">{errors.yosh.message}</p>}
<label>
<input type="checkbox" {...register('shartlar')} /> Shartlarga roziman
</label>
{errors.shartlar && <p role="alert">{errors.shartlar.message}</p>}
<button disabled={isSubmitting}>Ro'yxatdan o'tish</button>
</form>
);
}
register('ism')— endi qoidasiz; hamma qoida sxemadaresolver: zodResolver(schema)— RHF validatsiyani Zod ga topshiradionSubmitga keladigandata— tekshirilgan va o'zgartirilgan ma'lumot
z.coerce — tur o'zgartirish
z.coerce.number() // "25" → 25
z.coerce.date() // "2026-11-07" → Date
Input lar doim string beradi (62-dars). z.coerce uni kerakli turga aylantiradi — valueAsNumber kerak emas.
[!WARNING]
z.coerce.number()bo'sh string""ni 0 ga aylantiradi — "majburiy maydon" tekshiruvi ishlamay qoladi. Majburiy son maydoni uchunz.coerce.number().min(1, ...)yokiz.string().min(1, 'Kiriting').pipe(z.coerce.number())kabi naqshdan foydalaning.
Bog'liq maydonlar — refine
Parollar mosligi bir maydonga emas, ikki maydonga bog'liq — refine obyekt darajasida:
const parolSchema = z
.object({
parol: z
.string()
.min(8, 'Kamida 8 belgi')
.regex(/\d/, 'Kamida bitta raqam')
.regex(/[A-Z]/, 'Kamida bitta katta harf'),
parolTakror: z.string(),
})
.refine((d) => d.parol === d.parolTakror, {
message: 'Parollar mos kelmadi',
path: ['parolTakror'], // xato qaysi maydonda ko'rinadi
});
path siz xato errors.root ga tushadi — foydalanuvchi uni maydon yonida ko'rmaydi.
Shartli qoidalar
const buyurtmaSchema = z
.object({
yetkazish: z.enum(['kuryer', 'ozi']),
manzil: z.string().optional(),
})
.refine((d) => d.yetkazish !== 'kuryer' || (d.manzil && d.manzil.length > 5), {
message: 'Kuryer uchun manzilni kiriting',
path: ['manzil'],
});
Kuryer tanlanganda manzil majburiy, aks holda — yo'q.
TypeScript — tur avtomatik
const royxatSchema = z.object({
ism: z.string().min(2),
email: z.string().email(),
yosh: z.coerce.number(),
});
type RoyxatForma = z.infer<typeof royxatSchema>;
// { ism: string; email: string; yosh: number }
const { register } = useForm<RoyxatForma>({
resolver: zodResolver(royxatSchema),
});
register('isim'); // ❌ TypeScript xatosi — bunday maydon yo'q
z.infer — sxemadan tur chiqaradi. Tur va qoidalar hech qachon farq qilib qolmaydi: bitta manba.
Sxemani qayta ishlatish
// schemas/foydalanuvchi.js
export const emailSchema = z.string().trim().toLowerCase().email("Email noto'g'ri");
export const parolSchema = z.string().min(8, 'Kamida 8 belgi');
export const loginSchema = z.object({
email: emailSchema,
parol: z.string().min(1, 'Parolni kiriting'),
});
export const royxatSchema = z.object({
ism: z.string().min(2),
email: emailSchema,
parol: parolSchema,
});
export const profilSchema = royxatSchema.omit({ parol: true }).extend({
bio: z.string().max(300).optional(),
});
- Kichik sxemalardan kattalarini yig'ish
.omit(),.pick(),.extend(),.partial()— mavjud sxemadan yangisini yasash
Node.js backend da xuddi shu faylni import qilib, API ga kelgan ma'lumotni tekshirish mumkin — frontend va backend qoidalari doim bir xil.
Nima uchun shunday?
Nima uchun validatsiyani komponentdan alohida sxemaga chiqarish yaxshi amaliyot?
Chunki validatsiya qoidalari — bu biznes qoidalari, UI emas. "Foydalanuvchi 16 yoshdan katta bo'lishi kerak" — bu qoida forma qanday ko'rinishidan qat'i nazar mavjud: veb-saytda, mobil ilovada, API da, ma'lumotlar bazasiga yozishda. Uni JSX ichiga yozish — qoidani bitta ko'rinishga "qamab qo'yish" degani. Sxema esa qoidani ma'lumot sifatida ifodalaydi: uni import qilish, birlashtirish, testlash, serverda qayta ishlatish va undan TypeScript turini chiqarish mumkin. Bu dasturlashdagi eski tamoyil — "yagona haqiqat manbai" (single source of truth): har bir bilim tizimda faqat bir joyda yozilsin.
Xulosa
Eslab qoling:
- Zod — ma'lumot sxemasi:
z.object({ ism: z.string().min(2, 'xabar') }) npm install zod @hookform/resolvers;useForm({ resolver: zodResolver(schema) })register('nom')— qoidasiz;onSubmitga tekshirilgan va o'zgartirilgandatakeladiz.coerce.number()— string → son; bo'sh string 0 ga aylanishini unutmang.refine(fn, { message, path })— bog'liq maydonlar va shartli qoidalarz.infer<typeof schema>— TypeScript turi avtomatik- Sxemalarni bo'lish va qayta ishlatish:
.omit,.pick,.extend,.partial; backend da ham
Keyingi dars
66-dars: React Hook Form — murakkab formalar, massiv maydonlar