11.10-bob: Formalar — React Hook Form + Zod validation
11-QISM — Frontend: React · 10-mavzu
1. Kirish va motivatsiya
Forma — har ilovaning eng ko'p uchraydigan, lekin eng ko'p og'riq beradigan qismi: login, ro'yxatdan o'tish, checkout, profil sozlamalari, mahsulot qo'shish, izoh yozish — bularning hammasi forma. 11.4-bobda controlled inputsni qo'lda boshqardik (value + onChange har maydonga — 11.4: 2.11). Bu kichik formada (1-2 maydon) ishlaydi, lekin haqiqiy formada chidab bo'lmaydi: 15 maydonli ro'yxatdan o'tish formasi uchun 15 ta useState, 15 ta onChange, har harf yozilganda butun forma re-render bo'ladi (sekin), va validatsiya (email to'g'rimi? parol yetarli kuchlimi? parollar mosmi?) — bularni qo'lda yozish yuzlab qator zerikarli, xatoga moyil kod keltiradi.
Yechim — ikkita zamonaviy kutubxona oltin juftligi: React Hook Form (RHF) va Zod. React Hook Form formani samarali (kam re-render — uncontrolled yondashuv) va sodda boshqaradi: har maydonga useState yozmaysiz, register bilan ulashtiradi; submit, validatsiya, xato holatlari — hammasini RHF boshqaradi. Zod esa — validatsiya sxemasini (qoidalarni) deklarativ, type-safe yozadi: "email — to'g'ri email bo'lsin, parol — kamida 8 belgi, yosh — 18 dan katta" degan qoidalarni bir joyda aniq yozasiz, va TypeScript turlarini undan avtomatik chiqarasiz (5.9-bobda Zod'ni backend uchun ko'rganmiz — endi frontend forma kontekstida). Bu ikkisi birga — zamonaviy React forma standartini tashkil qiladi.
Bu bob: forma muammosi (controlled inputs ko'lamda — 11.4), controlled vs uncontrolled (RHF falsafasi — nega tez), React Hook Form (useForm, register, handleSubmit), formState (errors, isSubmitting, isValid, isDirty), built-in validatsiya qoidalari, Zod (sxema, type inference — 5.9 davomi), zodResolver (RHF + Zod ulanishi), xatolarni ko'rsatish (field-level), Controller (controlled komponentlar / UI kutubxonalar uchun), watch/setValue/getValues/reset, default values (tahrirlash formasi), async va server xatolari, useFieldArray (dinamik maydonlar), va accessibility (a11y — label, aria). Har bir mavzu to'liq, amaliy va zamonaviy holatda, izohli kod bilan ochib beriladi.
O'xshatish: Forma boshqaruvi — bu imtihon ishini tekshirish jarayoni. Controlled inputs 11.4-bob — bu o'qituvchi har bir o'quvchi har bir harf yozganda darrov tekshirib turishi (charchatadi, sekinlashtiradi — har keystroke re-render). React Hook Form — aqlli o'qituvchi: o'quvchi ishlayveradi (uncontrolled — input o'zi qiymatini tutadi, ref orqali), o'qituvchi faqat topshirishda (submit) yoki kerakli paytda tekshiradi (kam aralashuv — tez). Zod — bu baholash mezonlari varaqasi (rubrika): "javob to'g'ri bo'lishi uchun shu shartlar bajarilsin" — aniq, bir joyda, qayta ishlatiladigan. O'qituvchi har ishni o'zi xayolan tekshirmaydi (xato qiladi) — rubrikaga solishtiradi (izchil, ishonchli). Va eng yaxshisi — rubrika (Zod sxema) bir vaqtning o'zida javob kaliti (TypeScript turlari) ham bo'ladi.
Nega muhim?
- Har ilovada bor — forma'siz ilova yo'q (login, ro'yxat, checkout — hammasi forma).
- 11.4 og'rig'ini yo'qotadi — RHF katta formani oddiy va tez qiladi (kam re-render).
- Type-safe validatsiya — Zod sxemasidan TypeScript turlari avtomatik (bir manba — 5.9).
- Full-stack izchillik — Zod ham frontend forma, ham backend validatsiyada (bir til — 5.9, NestJS).
2. Nazariya — chuqur tushuntirish
2.1. Forma muammosi — controlled inputs ko'lamda
CONTROLLED INPUTS (11.4: 2.11) — kichik formada yaxshi, KATTA formada og'riq:
// 10 maydonli forma — controlled (qo'lda):
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// ... yana 7 ta useState
const [errors, setErrors] = useState({});
// har maydonda: value={x} onChange={e => setX(e.target.value)} — takror
// validatsiya: qo'lda har maydon uchun (email regex, parol uzunligi, mos kelish...)
MUAMMOLAR:
Ko'p useState (10 maydon = 10 state) — tarqoq
HAR harf yozilganda BUTUN forma re-render (sekin — 11.4: 2.4)
Validatsiya qo'lda (har qoida qo'lda yoziladi — xatoga moyil)
Xato holatlari, touched, submit — hammasini qo'lda boshqarish (yuzlab qator)
Controlled — kichik formada OK; katta formada RHF kerak (samarali, sodda — 2.2)
Forma — eng ko'p takrorlanadigan UI maxsus vosita (RHF) bilan hal qilinadiForma muammosi — controlled inputs (11.4: 2.11) kichik formada yaxshi ishlaydi, lekin katta formada (10+ maydon) og'riq beradi. 10 maydonli forma uchun: 10 ta
useState, har maydondavalue/onChange(takror), validatsiya holati (errors), va har qoida (email to'g'rimi, parol kuchlimi, parollar mos) qo'lda yoziladi. Muammolar: (1) ko'puseState(tarqoq, boshqarish qiyin); (2) har harf yozilganda butun forma re-render bo'ladi (sekin — 11.4: 2.4); (3) validatsiya qo'lda (har qoidani o'zingiz yozasiz — xatoga moyil, takrorlanadi); (4) xato holatlari, "tegilgan" (touched) maydonlar, submit jarayoni — hammasini qo'lda boshqarish (yuzlab qator zerikarli kod). Demak: controlled inputs kichik formada (1-3 maydon) maqbul, lekin katta/murakkab formada maxsus vosita kerak. Forma — ilovadagi eng ko'p takrorlanadigan va eng murakkab UI naqshlaridan biri, shuning uchun u uchun yetuk kutubxona (React Hook Form) ishlatiladi — xuddi routing uchun React Router 11.9-bob kabi.
2.2. Controlled vs uncontrolled — RHF falsafasi
CONTROLLED 11.4-bob — input qiymati REACT STATE'da (har harfda re-render):
<input value={name} onChange={e => setName(e.target.value)} />
// React har keystroke'ni biladi har harfda komponent re-render (sekin katta formada)
UNCONTROLLED (RHF) — input qiymati DOM'da (ref orqali — React aralashmaydi):
<input {...register("name")} /> // RHF ref bilan ulaydi (useState YO'Q)
// foydalanuvchi yozganda — React re-render BO'LMAYDI (DOM o'zi tutadi)
// qiymat faqat submit/kerakli paytda o'qiladi (ref'dan)
┌──────────────┬─────────────────────┬──────────────────────────┐
│ │ Controlled 11.4-bob │ Uncontrolled (RHF) │
├──────────────┼─────────────────────┼──────────────────────────┤
│ Qiymat qayerda│ React state │ DOM (ref) │
│ Re-render │ HAR harfda │ kam (submit/validatsiyada)│
│ Tezlik │ sekinroq (katta) │ TEZ (uncontrolled) │
│ Kod │ ko'p (har maydon) │ kam (register) │
└──────────────┴─────────────────────┴──────────────────────────┘
RHF UNCONTROLLED yondashuv har harfda re-render YO'Q KATTA formada tez
RHF qiymatni ref'dan o'qiydi (DOM'dan) React'ni "bezovta qilmaydi" (performance)Controlled vs uncontrolled — React Hook Form'ning tezligining siri. Controlled 11.4-bob: input qiymati React stateda yashaydi, har keystroke React'ga xabar beradi (
onChangesetState), shuning uchun har harfda komponent re-render bo'ladi (katta formada sekin). Uncontrolled (RHF): input qiymati DOM'da (brauzer input elementining o'zida) qoladi, RHF unga ref orqali ulanadi (11.5: 2.12) —<input {...register("name")} />—useStateyo'q. Foydalanuvchi yozganda React re-render qilmaydi (DOM o'zi qiymatni tutadi), RHF esa qiymatni faqat kerakli paytda (submit yoki validatsiya) ref'dan o'qiydi. Natija: RHF uncontrolled yondashuv tufayli har harfda re-render bo'lmaydi katta forma ham tez ishlaydi (11.4 controlled formasini sekinlashtirgan muammo yo'q). Va kod ham kam — har maydongauseState/onChangeo'rniga bittaregister. Bu — RHF'ning ikki asosiy afzalligi: tezlik (kam re-render) va soddalik (kam kod). Aynan shu sabab RHF zamonaviy React forma standartiga aylandi.
2.3. React Hook Form — useForm, register, handleSubmit
O'RNATISH: npm install react-hook-form
ASOSIY UCH ELEMENT:
import { useForm } from "react-hook-form";
function MyForm() {
const { register, handleSubmit } = useForm(); // 1. forma yaratiladi
const onSubmit = (data) => console.log(data); // 2. submit ishlovchi (data — barcha qiymat)
return (
<form onSubmit={handleSubmit(onSubmit)}> {/* 3. handleSubmit o'raydi (validatsiya + onSubmit) */}
<input {...register("name")} /> {/* register — maydonni ulaydi */}
<input {...register("email")} />
<button type="submit">Yuborish</button>
</form>
);
}
- useForm() — forma "instansi" (register, handleSubmit, formState...)
- register("x") — input'ni formaga ulaydi (name="x" + ref + onChange/onBlur — DOM)
- handleSubmit — submit'ni o'raydi: validatsiya O'Tsa onSubmit(data); xato bo'lsa onError
┌──────────────────────────────────────────────────────────┐
│ register: input forma | handleSubmit: validatsiya+submit │
│ onSubmit(data): data — BARCHA maydon qiymati (obyekt) │
└──────────────────────────────────────────────────────────┘
{...register("name")} — name/ref/event'larni input'ga "tarqatadi" (spread — 11.2)
data — submit'da avtomatik yig'ilgan barcha maydon qiymati (qo'lda yig'ish yo'q)React Hook Form uch asosiy element bilan ishlaydi.
useForm()— forma "instansi"ni yaratadi va kerakli vositalarni (register,handleSubmit,formState...) qaytaradi.register("name")— input'ni formaga ulaydi: uname,ref, vaonChange/onBlurhodisalarini qaytaradi, ularni{...register("name")}(spread — 11.2) bilan input'ga "tarqatasiz" — natijada RHF o'sha input'ni kuzatadi (DOM ref orqali — 2.2).handleSubmit(onSubmit)—<form onSubmit={...}>ga beriladi: u submit'ni o'raydi, avval validatsiyani bajaradi, agar o'tsaonSubmit(data)ni chaqiradi (data— barcha maydon qiymatlari obyekti — qo'lda yig'ish yo'q), agar xato bo'lsaonSubmitni chaqirmaydi. Ikki muhim nuqta: (1){...register("name")}— RHF'ning sirli qismi: u input'ganame/ref/hodisalarni spread qiladi (shuning uchunuseState/onChangeqo'lda kerak emas); (2)data— submit'da RHF avtomatik yig'gan barcha qiymatlar ({ name, email }). Bu uch element — RHF'ning yadrosi; qolgani (validatsiya, xato, holatlar) ularga qo'shiladi.
2.4. formState — forma holatlari (errors, isSubmitting...)
formState — forma haqida BARCHA holat ma'lumoti:
const { register, handleSubmit, formState } = useForm();
const { errors, isSubmitting, isValid, isDirty, touchedFields } = formState;
MUHIM HOLATLAR:
errors — validatsiya xatolari (errors.email?.message — maydon xatosi — 2.8)
isSubmitting — submit jarayonida (async submit paytida true — tugma disable)
isValid — barcha maydon valid (forma to'g'ri to'ldirilgan)
isDirty — forma o'zgartirilgan (default'dan farq — "saqlanmagan o'zgarish" ogohlantirish)
touchedFields — qaysi maydonlar "tegilgan" (fokus olib-ketgan — xatoni qachon ko'rsatish)
dirtyFields — qaysi maydonlar o'zgartirilgan
ISHLATISH:
<button disabled={isSubmitting || !isValid}> {/* submit paytida yoki noto'g'rida disable */}
{isSubmitting ? "Yuborilmoqda..." : "Yuborish"}
</button>
formState — formani BOSHQARISH uchun (tugma holati, xato ko'rsatish, ogohlantirish)
isSubmitting — async submit'da tugmani disable (ikki marta yuborishni oldini olish)
formState— forma haqidagi barcha holat ma'lumotini beruvchi obyekt (useForm()qaytaradi). Muhim holatlar:errors— validatsiya xatolari (har maydon uchun —errors.email?.message— 2.8);isSubmitting— submit jarayonida (async submit paytidatrue— tugmani disable qilish uchun);isValid— barcha maydon valid (forma to'g'ri to'ldirilgan);isDirty— forma o'zgartirilgan (default qiymatdan farq — "saqlanmagan o'zgarish bor" ogohlantirishi uchun);touchedFields— qaysi maydonlar "tegilgan" (foydalanuvchi fokusga olib-ketgan — xatoni qachon ko'rsatishni hal qilish uchun);dirtyFields— qaysi maydonlar o'zgartirilgan. Ishlatish:<button disabled={isSubmitting || !isValid}>— submit paytida yoki forma noto'g'ri to'ldirilganda tugmani o'chiradi. Ikki amaliy nuqta: (1)formState— formani boshqarish uchun (tugma holati, xato ko'rsatish, ogohlantirish — UX); (2)isSubmittingayniqsa muhim — async submit (API so'rovi) paytida tugmani disable qilib, foydalanuvchi formani ikki marta yubormasligini ta'minlaydi (idempotency — 8.20).formState— RHF'ning forma UX'ini boshqarishning kalitidir.
2.5. Built-in validatsiya qoidalari
RHF register ICHIDA validatsiya qoidalari (Zod'siz — oddiy holatlar):
<input {...register("email", {
required: "Email majburiy", // bo'sh bo'lmasin (xato matni)
pattern: { value: /^\S+@\S+$/, message: "Email noto'g'ri" }, // regex (2.13 RegEx)
})} />
<input {...register("age", {
required: "Yosh majburiy",
min: { value: 18, message: "18 dan katta bo'lsin" },
max: { value: 100, message: "Noto'g'ri yosh" },
valueAsNumber: true, // string number (input qiymati string — 11.4)
})} />
<input {...register("password", {
minLength: { value: 8, message: "Kamida 8 belgi" },
validate: (v) => v.includes("!") || "Maxsus belgi kerak", // maxsus qoida (funksiya)
})} />
QOIDALAR: required, min/max, minLength/maxLength, pattern, validate (custom)
Built-in — oddiy forma uchun yetadi; MURAKKAB/qayta ishlatiladigan Zod (2.6)
valueAsNumber/valueAsDate — input string'ni to'g'ri turga o'giradiBuilt-in validatsiya — RHF'ning
registerichida to'g'ridan validatsiya qoidalari belgilash (Zod'siz, oddiy holatlar uchun).register("email", { required: "...", pattern: {...} })— ikkinchi argument — qoidalar obyekti. Qoidalar:required(bo'sh bo'lmasin),min/max(raqam chegarasi),minLength/maxLength(matn uzunligi),pattern(regex — 2.13 RegEx),validate(custom funksiya —v => shart || "xato matni"). Har qoida{ value, message }shaklida (qiymat + xato matni) yoki to'g'ridan qiymat bo'lishi mumkin. Muhim:valueAsNumber/valueAsDate— input qiymati har doim string 11.4-bob, bu opsiya uni avtomatik to'g'ri turga (number/date) o'giradi. Ikki nuqta: (1) built-in validatsiya oddiy forma uchun yetadi (login, oddiy kontakt); (2) lekin murakkab (parollar mosligi, shartli qoidalar, ichma-ich obyektlar) yoki qayta ishlatiladigan (frontend + backend bir xil qoida) validatsiya uchun — Zod 2.6-bob ancha kuchli va tartibli. Amalda ko'p loyiha to'g'ridan Zod ishlatadi (built-in'ni o'tkazib).
2.6. Zod — sxema va type inference (5.9 davomi)
ZOD — validatsiya SXEMASI (qoidalar) + TypeScript turlari (BIR manbadan — 5.9):
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email("Email noto'g'ri"), // string + email format
password: z.string().min(8, "Kamida 8 belgi"), // string + min uzunlik
age: z.number().min(18, "18 dan katta").max(100), // number + chegara
role: z.enum(["user", "admin"]), // faqat shu qiymatlar
agree: z.boolean().refine(v => v, "Rozilik kerak"), // custom (refine — 2.13)
});
TYPE INFERENCE — sxemadan TypeScript turini AVTOMATIK chiqarish:
type LoginForm = z.infer<typeof loginSchema>;
// { email: string; password: string; age: number; role: "user"|"admin"; agree: boolean }
// (qo'lda interface yozish YO'Q — sxema = yagona manba — 5.9)
┌──────────────────────────────────────────────────────────┐
│ Zod sxema = validatsiya qoidalari + TypeScript turi (bir │
│ joyda) "single source of truth" (takror yo'q) │
└──────────────────────────────────────────────────────────┘
Zod — backend'da ham (NestJS/Express — 5.9); frontend forma bilan BIR XIL sxema (DRY)
z.infer — sxemadan tur (qo'lda interface yozish shart emas — avtomatik, doim mos)Zod — validatsiya sxemasi va TypeScript turlarini bir manbadan beruvchi kutubxona (5.9-bobda backend uchun ko'rganmiz).
z.object({...})bilan forma shaklini va qoidalarini deklarativ yozasiz:z.string().email("...")(string + email format),z.string().min(8, "...")(min uzunlik),z.number().min(18),z.enum([...])(faqat shu qiymatlar),z.boolean().refine(...)(custom qoida — 2.13). Har validatorga xato matni beriladi. Type inference — Zod'ning eng kuchli imkoniyati:type LoginForm = z.infer<typeof loginSchema>— sxemadan TypeScript turini avtomatik chiqaradi ({ email: string; password: string; ... }), shuning uchun qo'ldainterfaceyozish kerak emas — sxema yagona manba (qoidalar + tur bir joyda). Ikki muhim afzallik: (1) DRY/full-stack izchillik — aynan o'sha Zod sxemani backend'da ham ishlatasiz (NestJS/Express validatsiya — 5.9), shuning uchun frontend va backend bir xil qoidaga tayanadi (mos kelmaslik yo'q); (2)z.infer— sxema o'zgarsa tur avtomatik yangilanadi (qo'lda interface bilan sinxronlash muammosi yo'q). Zod — type-safe, qayta ishlatiladigan validatsiyaning zamonaviy standarti.
2.7. zodResolver — RHF va Zod ulanishi
RHF + Zod ulanishi — @hookform/resolvers/zod orqali (zodResolver):
npm install zod @hookform/resolvers
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
email: z.string().email("Email noto'g'ri"),
password: z.string().min(8, "Kamida 8 belgi"),
});
type FormData = z.infer<typeof schema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema), // Zod sxemani RHF'ga ULAYDI
});
const onSubmit = (data: FormData) => console.log(data); // data — TYPE-SAFE (z.infer)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("email")} />
{errors.email && <span>{errors.email.message}</span>} {/* Zod xato matni 2.8-bob */}
<input type="password" {...register("password")} />
{errors.password && <span>{errors.password.message}</span>}
<button>Kirish</button>
</form>
);
}
┌──────────────────────────────────────────────────────────┐
│ resolver: zodResolver(schema) — RHF validatsiyani Zod'ga │
│ topshiradi; xatolar errors'da (Zod matni bilan) │
└──────────────────────────────────────────────────────────┘
resolver — RHF'ga "validatsiyani Zod qiladi" deydi (built-in o'rniga — 2.5)
data — z.infer turida (type-safe submit — TypeScript xatoni tutadi)
zodResolver— RHF va Zod'ni ulaydigan ko'prik (@hookform/resolverspaketidan). Ulanish:useForm({ resolver: zodResolver(schema) })— bu RHF'ga "validatsiyani Zod sxemasi qiladi" deydi (built-in qoidalar o'rniga — 2.5). Endi forma yuborilganda RHF avtomatik Zod sxemasiga solishtiradi: agar valid bo'lsaonSubmit(data)chaqiriladi, aks holda xatolarformState.errorsga (Zod xato matnlari bilan) tushadi. Type-safe:type FormData = z.infer<typeof schema>bilan,onSubmit(data: FormData)—datato'liq turlangan (TypeScript autocomplete, xatoni compile paytida tutadi). Bu — zamonaviy React forma'sining to'liq naqshi: Zod sxema (qoida + tur)zodResolverRHF (boshqaruv) type-safe submit. Ikki afzallik: (1) validatsiya markazlashgan (Zod sxemada, har inputda tarqoq emas — toza); (2) sxema o'zgarsa qoida, tur, va forma — hammasi bir joydan yangilanadi.react-hook-form+zod+@hookform/resolvers— bu uchligi 2026 React forma standarti. Esda tuting: bu yerdaresolver11.9'dagi React Router'ningloader/resolveridan butunlay boshqa (nom o'xshashligi tasodif).
2.8. Xatolarni ko'rsatish — field-level errors
errors obyektidan maydon xatolari ko'rsatiladi (formState.errors — 2.4):
{errors.email && <span className="error">{errors.email.message}</span>}
// └─ maydon xatosi (bor bo'lsa) └─ xato matni (Zod/qoida — 2.6, 2.5)
TOZA NAQSH (xato + a11y — 2.14):
<div>
<label htmlFor="email">Email</label>
<input
id="email"
{...register("email")}
aria-invalid={errors.email ? "true" : "false"} // a11y — ekran o'quvchi 2.14-bob
aria-describedby="email-error"
/>
{errors.email && (
<span id="email-error" role="alert" className="error">{errors.email.message}</span>
)}
</div>
QACHON xato ko'rsatish (mode):
useForm({ mode: "onBlur" }) // fokus ketganda tekshir (yumshoq — yozayotganda bezovta qilmaydi)
useForm({ mode: "onChange" }) // har o'zgarishda (qattiq — jonli)
useForm({ mode: "onSubmit" }) // faqat submit'da (default — eng yumshoq)
mode — UX: onBlur (tavsiya) — foydalanuvchi yozayotganda emas, ketganda xato
Xatoni label/input bilan bog'la (aria-describedby — a11y — 2.14)Xatolarni ko'rsatish —
formState.errorsobyektidan maydon xatolari ko'rsatiladi. Eng oddiy:{errors.email && <span>{errors.email.message}</span>}—errors.emailbor bo'lsa, uningmessageini (Zod yoki built-in qoidadan — 2.6, 2.5) chiqaradi. Toza naqsh (a11y bilan — 2.14):<label>+<input>+ xato<span>, vaaria-invalid(input xato holatini ekran o'quvchiga bildiradi),aria-describedby(input'ni xato matni bilan bog'laydi),role="alert"(xato paydo bo'lganda o'qiladi). Qachon xato ko'rsatish (modeopsiyasi):mode: "onSubmit"(default — faqat submit'da, eng yumshoq),mode: "onBlur"(maydondan fokus ketganda — yumshoq, foydalanuvchini yozayotgan paytda bezovta qilmaydi — tavsiya),mode: "onChange"(har o'zgarishda — jonli, lekin qattiq). Ikki nuqta: (1)mode: "onBlur"odatda eng yaxshi UX (foydalanuvchi maydonni tugatib, keyingisiga o'tganda tekshiriladi); (2) xatoni input/label bilan a11y orqali bog'lash majburiy (ekran o'quvchi foydalanuvchilari xato qaysi maydonda ekanini bilishi kerak — 2.14). To'g'ri xato ko'rsatish — formaning foydalanuvchi tajribasining yarmi.
2.9. Controller — controlled komponentlar va UI kutubxonalar
MUAMMO: RHF register oddiy <input>/<select> bilan ishlaydi (uncontrolled — 2.2)
LEKIN UI kutubxona komponentlari (MUI Select, react-select, DatePicker) CONTROLLED
(ular value/onChange kutadi, ref'ni to'g'ridan bermaydi) register ishlamaydi
YECHIM — <Controller> (RHF'ni controlled komponentga "ko'prik"):
import { Controller } from "react-hook-form";
<Controller
name="country"
control={control} // useForm()'dan control
render={({ field }) => (
<Select {...field} options={countries} /> // field: value, onChange, onBlur, ref, name
)}
/>
// field.value / field.onChange — Controller controlled komponentga beradi (RHF bilan sinxron)
┌──────────────────────────────────────────────────────────┐
│ register: oddiy input (uncontrolled — tez) │
│ Controller: UI kutubxona / controlled komponent (ko'prik) │
└──────────────────────────────────────────────────────────┘
Oddiy <input>/<select> register 2.3-bob; UI kutubxona komponenti Controller
control — useForm() qaytaradi (Controller'ga beriladi)
Controller— RHF'ni controlled komponentlar bilan ulaydigan ko'prik. Muammo: RHF'ningregister'i oddiy<input>/<select>bilan ishlaydi (uncontrolled — ref orqali — 2.2), lekin UI kutubxona komponentlari (MUI Select, react-select, DatePicker, rich text editor) controlled (ularvalue/onChangekutadi, DOM ref'ni to'g'ridan bermaydi) — shuning uchunregisterularda ishlamaydi. Yechim —<Controller>: uname,control(useForm()dan), varenderfunksiyasini oladi;renderfieldobyektini beradi (value,onChange,onBlur,ref,name), uni controlled komponentga{...field}bilan tarqatasiz — Controller controlled komponentni RHF bilan sinxronlaydi (qiymatni RHF holatiga ulaydi). Ikki nuqta: (1) qachon nima: oddiy<input>/<select>/<textarea>register(tez, oddiy — 2.3); UI kutubxona yoki custom controlled komponentController; (2)control—useForm()qaytaradigan obyekt, uniControllerga berasiz. Amalda formaning ko'p qismiregister, faqat UI kutubxona maydonlari (sana tanlagich, ko'p tanlovli select)Controllerbilan bo'ladi. Bu — RHF'ni har qanday komponent bilan ishlatishning kaliti.
2.10. watch, setValue, getValues, reset
FORMANI DASTURIY BOSHQARISH (qiymatni o'qish/yozish/kuzatish):
watch — maydonni KUZATISH (o'zgarganda re-render — shartli UI uchun):
const accountType = watch("accountType"); // bu maydon o'zgarganda komponent re-render
{accountType === "business" && <input {...register("companyName")} />} // shartli maydon
setValue — maydonga QIYMAT berish (dasturiy):
setValue("city", "Toshkent"); // masalan select'ga qarab avtomatik to'ldirish
getValues — qiymatni O'QISH (re-render'siz — submit'siz tekshirish):
const email = getValues("email"); // joriy qiymat (kuzatmay — bir martalik o'qish)
reset — formani QAYTA TIKLASH (tozalash yoki yangi qiymatlar):
reset(); // default'ga (yoki bo'sh)
reset({ name: "Ali", email: "ali@x.uz" }); // yangi qiymatlar bilan (tahrirlash — 2.11)
┌──────────────────────────────────────────────────────────┐
│ watch: kuzat (re-render) | getValues: o'qi (re-render yo'q)│
│ setValue: yoz | reset: qayta tikla (tozala/to'ldir) │
└──────────────────────────────────────────────────────────┘
watch — shartli UI uchun (re-render keltiradi); getValues — bir martalik o'qish (tez)
reset(data) — submit'dan keyin tozalash yoki tahrirlash formasini to'ldirish (2.11)
watch,setValue,getValues,reset— formani dasturiy boshqarish vositalari (useForm()qaytaradi).watch("field")— maydonni kuzatadi: u o'zgarganda komponent re-render bo'ladi (shartli UI uchun — masalanaccountType === "business"bo'lsa qo'shimcha maydon ko'rsatish).setValue("city", "Toshkent")— maydonga dasturiy qiymat beradi (masalan boshqa maydonga qarab avtomatik to'ldirish, yoki API'dan kelgan ma'lumot).getValues("email")— qiymatni re-render'siz o'qiydi (bir martalik tekshirish —watch'dan farqli, kuzatmaydi).reset()— formani qayta tiklaydi: argumentsiz default qiymatlarga (yoki bo'sh),reset({ name, email })esa yangi qiymatlar bilan (tahrirlash formasini to'ldirish yoki submit'dan keyin tozalash — 2.11). Ikki tafovutni yodda tuting: (1)watchre-render keltiradi (shartli UI uchun kerak),getValueskeltirmaydi (faqat bir martalik o'qish — tezroq, kuzatish kerak bo'lmaganda); (2)resetikki vazifa qiladi — submit muvaffaqiyatli bo'lgach formani tozalash, yoki tahrirlash formasini API ma'lumoti bilan to'ldirish. Bu to'rt vosita — formaning dinamik (shartli, avtomatik to'ldiriladigan, tahrirlanadigan) holatlarini boshqaradi.
2.11. Default values va tahrirlash formasi
DEFAULT VALUES — formaning boshlang'ich qiymatlari (tahrirlash formasi uchun muhim):
YARATISH formasi (bo'sh default):
const { register } = useForm({
defaultValues: { name: "", email: "", role: "user" },
});
TAHRIRLASH formasi (mavjud ma'lumot bilan to'ldirish — API'dan keladi):
function EditProfile({ userId }) {
const { register, reset, handleSubmit } = useForm();
const { data: user } = useFetch(`/api/users/${userId}`); // 11.7
useEffect(() => {
if (user) reset(user); // ma'lumot kelgach — formani to'ldir (reset — 2.10)
}, [user, reset]);
// ...
}
// YOKI defaultValues async funksiya (RHF v7+):
// useForm({ defaultValues: async () => fetch(...).then(r => r.json()) });
┌──────────────────────────────────────────────────────────┐
│ defaultValues: boshlang'ich (yaratish — bo'sh; tahrir — ma'lumot)│
│ reset(data): API ma'lumoti kelgach formani to'ldir (useEffect)│
└──────────────────────────────────────────────────────────┘
defaultValues — controlled/uncontrolled'ni to'g'ri boshlaydi (Xato: undefined controlled warning — 11.4)
Tahrir formasi: ma'lumot async kelsa — useEffect + reset(data) (defaultValues hali bo'sh edi)Default values va tahrirlash formasi — formaning boshlang'ich holatini boshqaradi.
defaultValues—useForm({ defaultValues: {...} })bilan formaning boshlang'ich qiymatlari belgilanadi. Yaratish formasi: bo'sh default ({ name: "", email: "" }) — bu shuningdek "uncontrolled controlled" ogohlantirishini oldini oladi (11.4 —undefinedqiymatdan boshlama). Tahrirlash formasi (mavjud ma'lumotni o'zgartirish): ma'lumot serverdan async keladi, shuning uchundefaultValuesboshlang'ich render'da hali bo'sh — yechim: ma'lumot kelgachuseEffectichidareset(user)bilan formani to'ldirasiz 2.10-bob. Yoki RHF v7+ dadefaultValuesga async funksiya berish mumkin (async () => fetch(...)). Ikki muhim nuqta: (1)defaultValues— formani to'g'ri boshlaydi (har maydonga boshlang'ich qiymat —undefinedwarning'dan saqlaydi); (2) tahrirlash formasining standart naqshi: ma'lumotniuseFetch11.7-bob yoki Query 12.4-bob bilan oluseEffectdareset(data)bilan formani to'ldir. Bu — har CRUD ilovasining "edit" sahifasining asosi (mahsulot tahrirlash, profil yangilash). Default values'ni to'g'ri qo'yish — formaning ishonchli ishlashining poydevori.
2.12. Async submit va server xatolari
ASYNC SUBMIT — onSubmit serverga so'rov yuboradi (loading + server xatolari):
const { register, handleSubmit, setError, formState: { isSubmitting } } = useForm({
resolver: zodResolver(schema),
});
async function onSubmit(data) {
try {
await api.register(data); // server so'rovi (isSubmitting avtomatik true)
reset(); // muvaffaqiyatli tozala
navigate("/dashboard"); // yo'naltir 11.9-bob
} catch (err) {
// SERVER XATOSI (masalan "email band") maydonga yoki umumiy xato:
if (err.field) {
setError(err.field, { message: err.message }); // maydon xatosi (server'dan)
} else {
setError("root", { message: "Xatolik yuz berdi" }); // umumiy forma xatosi
}
}
}
<button disabled={isSubmitting}> {/* submit paytida disable (ikki marta yo'q) */}
{isSubmitting ? "Yuborilmoqda..." : "Ro'yxatdan o'tish"}
</button>
{errors.root && <p className="error">{errors.root.message}</p>} {/* umumiy xato */}
setError — server xatosini formaga qo'shadi (frontend validatsiya yetmagan narsa — masalan "email band")
isSubmitting — async submit'ni avtomatik kuzatadi (qo'lda loading state kerak emas)Async submit va server xatolari — formaning serverga ulanish qismi.
onSubmitasync bo'lganda (API so'rovi), RHFisSubmittingni avtomatik kuzatadi (qo'ldaloadingstate kerak emas — submit boshlangandatrue, tugagandafalse). Naqsh:tryichidaawait api.register(data)muvaffaqiyatli bo'lsareset()(tozalash) +navigate11.9-bob;catchichida server xatolarini boshqarish. Server xatolari — frontend validatsiya tuta olmaydigan xatolar (masalan "bu email allaqachon band", "parol noto'g'ri") — ular faqat serverdan keladi. BularnisetErrorbilan formaga qo'shasiz:setError("email", { message: "Email band" })(aniq maydonga) yokisetError("root", { message: "..." })(umumiy forma xatosi —errors.rootorqali ko'rsatiladi). Tugmadisabled={isSubmitting}— submit paytida o'chiriladi (ikki marta yuborishni oldini oladi). Ikki muhim nuqta: (1) frontend va server validatsiya birga — Zod frontend'da darrov tekshiradi (UX), server esa haqiqiy va xavfsiz tekshiradi (14-QISM),setErrorserver xatosini formaga "qaytaradi"; (2)isSubmittingasync submit UX'ini (loading tugma) avtomatik boshqaradi. Bu — real, serverga ulangan formaning to'liq naqshi.
2.13. useFieldArray — dinamik maydonlar
DINAMIK MAYDONLAR — soni o'zgaradigan maydon ro'yxati (telefon raqamlari, ta'lim, mahsulot variantlari):
import { useFieldArray, useForm } from "react-hook-form";
function Form() {
const { control, register, handleSubmit } = useForm({
defaultValues: { phones: [{ number: "" }] }, // boshlang'ich 1 ta
});
const { fields, append, remove } = useFieldArray({ control, name: "phones" });
return (
<form>
{fields.map((field, index) => (
<div key={field.id}> {/* field.id — RHF beradi (index emas! 11.4: 2.13) */}
<input {...register(`phones.${index}.number`)} /> {/* ichma-ich nom */}
<button type="button" onClick={() => remove(index)}>O'chir</button>
</div>
))}
<button type="button" onClick={() => append({ number: "" })}>+ Telefon qo'sh</button>
</form>
);
}
useFieldArray QAYTARADI:
fields — joriy elementlar (key={field.id} — barqaror)
append — oxiriga qo'sh remove — o'chir insert/move/swap — boshqa amallar
key={field.id} (RHF beradi — barqaror; index ISHLATMA — 11.4: 2.13)
Maydon nomi: `phones.${index}.number` (ichma-ich — massiv ichidagi obyekt)
useFieldArray— soni o'zgaradigan dinamik maydonlar (telefon raqamlari, ta'lim tarixi, mahsulot variantlari, savatdagi mahsulotlar).useFieldArray({ control, name: "phones" })— massiv maydonni boshqaradi va qaytaradi:fields(joriy elementlar — har birida RHF bergan barqarorid),append(oxiriga qo'shish),remove(index)(o'chirish), shuningdekinsert/move/swap/prepend. Render:fields.map((field, index) => ...)— har element uchunregister(\phones.${index}.number`)(ichma-ich maydon nomi — massiv ichidagi obyekt), va. Ikki kritik nuqta: (1) **key={field.id}** — RHF bergan barqaroridni ishlat, **key={index}emas** (11.4: 2.13 — ro'yxat o'zgarganda index buziladi, RHF'ningfield.idesa barqaror); (2) maydon nomi **ichma-ich** (phones.0.number,phones.1.number) — RHF buni avtomatik massiv obyektiga aylantiradi ({ phones: [{ number }, { number }] }).useFieldArray— murakkab formalarning (ko'p elementli, foydalanuvchi qo'shadigan/o'chiradigan) standart yechimi. Zod bilan ham ishlaydi (z.array(z.object({...}))`).2.14. Accessibility va best practices
textFORMA A11Y (accessibility — har kim ishlata olsin — 1.9): Har input'ga <label htmlFor="id"> (yoki aria-label) — ekran o'quvchi o'qiydi aria-invalid={!!errors.x} — xato holatini bildiradi aria-describedby="x-error" — input'ni xato matni bilan bog'laydi role="alert" xato matnida — paydo bo'lganda o'qiladi <button type="submit"> (type="button" — submit qilmaydi) Klaviatura bilan to'liq ishlasin (tab, enter) FORMA BEST PRACTICES: register — oddiy input; Controller — UI kutubxona 2.9-bob Validatsiya Zod sxemada (markazlashgan, type-safe, backend bilan bir xil — 2.6) mode: "onBlur" (yumshoq UX — 2.8) isSubmitting — tugmani disable (ikki marta yo'q — 2.12) Server xatolarni setError bilan 2.12-bob defaultValues (controlled warning'dan saqlaydi — 2.11) Forma a11y — majburiy (ekran o'quvchi, klaviatura foydalanuvchilari — 1.9, 14) Frontend validatsiya — UX; xavfsizlik HAR DOIM backend'da (Zod ikki joyda — 5.9, 14)Accessibility va best practices — formaning sifatli yakuni. Forma a11y 1.9-bob: har input'ga
<label htmlFor="id">(yokiaria-label) — ekran o'quvchi maydon nomini o'qiydi;aria-invalid(xato holati),aria-describedby(input'ni xato matni bilan bog'laydi),role="alert"(xato paydo bo'lganda avtomatik o'qiladi);<button type="submit">(type="button"submit qilmaydi — diqqat); va klaviatura bilan to'liq ishlash (tab, enter). Forma best practices:register(oddiy input) +Controller(UI kutubxona — 2.9); validatsiya Zod sxemada (markazlashgan, type-safe, backend bilan bir xil — 2.6);mode: "onBlur"(yumshoq UX — 2.8);isSubmittingbilan tugmani disable (ikki marta yuborish yo'q — 2.12); server xatolarinisetErrorbilan 2.12-bob;defaultValues(boshlang'ich, warning'dan saqlaydi — 2.11). Ikki yakuniy tamoyil: (1) forma a11y majburiy — ekran o'quvchi va klaviatura foydalanuvchilari (ko'plab odam) formani ishlata olishi kerak (1.9, qonuniy talab ham); (2) frontend validatsiya faqat UX uchun — haqiqiy xavfsizlik har doim backend'da (Zod sxemani backend'da ham ishlatib — 5.9, 14-QISM — bir manba, ikki joy). Bu tamoyillar formani professional darajaga olib chiqadi.2.15. useController — Controller'ning hook varianti (MUI, AntD)
textuseController — Controller BILAN BIR XIL, lekin HOOK ko'rinishida (render-prop o'rniga): import { useController } from "react-hook-form"; function TextField({ name, control, label }) { const { field, fieldState } = useController({ name, control }); // field: { value, onChange, onBlur, ref, name } — controlled komponentga // fieldState: { error, isDirty, isTouched, invalid } — maydon holati return ( <div> <label>{label}</label> <input {...field} /> {fieldState.error && <span>{fieldState.error.message}</span>} </div> ); } QACHON: qayta ishlatiladigan maxsus maydon komponenti yozganda (Controller — bir martalik) UI KUTUBXONALAR (controlled — register ishlamaydi Controller/useController): - MUI (Material UI): <Controller render={({field}) => <TextField {...field} .../>} /> - Ant Design (AntD): <Controller render={({field}) => <Input {...field} />} /> - react-select, DatePicker, rich-text — barchasi shu naqsh (2.9) useController — Controller'ning ichki "yuragi"; maxsus reusable maydonga qulay (2.16) Controller (render-prop) — tez, bir martalik; useController (hook) — komponentga o'ralganda
useController—Controllerning hook ko'rinishi: bir xil ish (RHF'ni controlled komponentga ulash — 2.9), lekinrenderprop o'rniga to'g'ridan hook chaqiriladi.useController({ name, control })ikki narsa qaytaradi:field(value,onChange,onBlur,ref,name— controlled komponentga{...field}bilan beriladi) vafieldState(error,isDirty,isTouched,invalid— o'sha maydon holati). QachonControllervsuseController:Controller(render-prop) — formada bir martalik UI-kutubxona maydoni uchun tez va qulay;useController— o'zingizning qayta ishlatiladigan maydon komponentingizni (masalan<TextField>,<CountrySelect>) yozganingizda, hook uni tabiiy va toza qiladi 2.16-bob. UI kutubxonalar integratsiyasi: MUI (<TextField>,<Select>), Ant Design (<Input>,<Select>), react-select, DatePicker, rich-text editorlar — bularning hammasi controlled (value/onChangekutadi, DOM ref bermaydi), shuning uchunregisterishlamaydi; ularniControlleryokiuseControllerbilan o'raysiz —field.value/field.onChangeorqali RHF bilan sinxronlanadi. Amaliy tanlov: oddiy<input>register; bir martalik UI-kutubxona maydoniController; qayta ishlatiladigan maydon komponentiuseController. Bu uchtasi — RHF'ni har qanday inputga ulashning to'liq to'plami.2.16. Qayta ishlatiladigan forma komponenti
textMUAMMO: har formada takror <label> + <input> + {errors.x && <span>} — takror kod YECHIM — reusable maydon komponenti (register'ni prop sifatida uzatish): function Field({ label, name, register, error, type = "text", ...rest }) { return ( <div className="field"> <label htmlFor={name}>{label}</label> <input id={name} type={type} {...register(name)} // register uzatiladi 2.3-bob aria-invalid={error ? "true" : "false"} // a11y 2.14-bob /> {error && <span role="alert" className="error">{error.message}</span>} </div> ); } ISHLATISH (forma qisqaradi): <Field label="Email" name="email" register={register} error={errors.email} /> <Field label="Parol" name="password" type="password" register={register} error={errors.password} /> register/error prop orqali bir marta yoz, hamma formada ishlat (DRY) Controlled UI-kutubxona maydoni uchun — useController bilan reusable (2.15)Qayta ishlatiladigan forma komponenti — takror kodni yo'qotish. Har formada bir xil
<div>+<label>+<input>+{errors.x && <span>}naqshi takrorlanadi (ayniqsa a11y atributlari bilan — 2.14). Yechim —<Field>kabi umumiy maydon komponentini bir marta yozib,label,name,register,errorproplarini uzatish. Ikki yondashuv: (1) oddiy input uchun —registerfunksiyasini vaerrorni prop sifatida uzatasiz (yuqoridagi<Field>); (2) controlled UI-kutubxona maydoni uchun — komponent ichidauseController2.15-bob ishlatasiz (controlni prop sifatida olib). Natijada forma JSX'i qisqaradi, a11y atributlari markazlashadi (bir joyda to'g'ri qilinsa — hamma joyda to'g'ri), va dizayn tizimi (design system) izchil bo'ladi. Bu — real loyihalarda muhim: 20+ formali ilovada har maydonga a11y va xato ko'rsatishni qo'lda yozish xatoga moyil; reusable komponent buni bir joyga to'playdi. TypeScript bilan (14-QISM) proplarni turlash (register: UseFormRegister<T>) yanada xavfsiz qiladi.2.17. Async validatsiya — server tekshiruv va debounce
textASYNC VALIDATSIYA — qoida serverga bog'liq (masalan "bu username band-mi?"): // 1) RHF built-in validate — async funksiya bo'lishi mumkin: <input {...register("username", { validate: async (value) => { const res = await fetch(`/api/check-username?u=${value}`); const { available } = await res.json(); return available || "Bu username band"; // false xato matni }, })} /> // 2) Zod bilan async — .refine ASYNC (mode "onBlur" tavsiya — har harfda so'rov yubormaslik): const schema = z.object({ email: z.string().email().refine( async (email) => await isEmailFree(email), // server so'rovi "Bu email ro'yxatdan o'tgan" ), }); // async refine zodResolver'da async mode avtomatik ishlaydi DEBOUNCE — har harfda emas, TO'XTAGANDA tekshir (server'ni bombardimon qilmaslik): - mode: "onBlur" (fokus ketganda — eng sodda debounce o'rnini bosadi) - yoki watch + useDebounce (12.x) + qo'lda tekshir (jonli "band-mi" ko'rsatkich) Async validatsiya — server bilishi kerak bo'lgan qoida (username/email band, promo-kod) Debounce/onBlur SHART — har keystroke'da fetch = server DDoS + sekin UXAsync validatsiya — qoidani tekshirish uchun server kerak bo'lgan holatlar: "bu username band-mi?", "bu email ro'yxatdan o'tganmi?", "promo-kod haqiqiymi?" — bularni faqat backend biladi (frontend Zod tuta olmaydi). Ikki usul: (1) RHF
validatefunksiyasi async bo'lishi mumkin — ichidafetchqilib,true(valid) yoki xato matni qaytaradi; (2) Zod.refineham async funksiya qabul qiladi (refine(async v => await check(v), "...")) —zodResolverbuni avtomatik async rejimda ishlatadi. Muhim — debounce: async validatsiyani har keystroke'da ishlatish serverga har harfda so'rov yuboradi (server yuki, sekin UX) — buni oldini olish shart. Eng sodda yechim —mode: "onBlur"(faqat maydondan fokus ketganda tekshiriladi); yanada nozik "jonli band-mi?" ko'rsatkich uchunwatch+useDebounce(so'rovni foydalanuvchi to'xtaganda yuborish — 12-QISM). Ikki tamoyil: (1) async validatsiya — server bilishi kerak bo'lgan noyoblik/mavjudlik qoidalari uchun; (2) debounce yokionBlurmajburiy — aks holda har harf server so'rovi. Va bu frontend tekshiruv — server submit'da yana haqiqiy tekshiradi (ikki foydalanuvchi bir vaqtda band qilishi mumkin — race — 14-QISM).2.18. Fayl yuklash (file upload)
textFAYL INPUT — <input type="file"> RHF bilan (fayl — string emas, FileList): <input type="file" {...register("avatar")} accept="image/*" /> // register("avatar") e.target.files (FileList) RHF'da saqlanadi onSubmit'da fayl — data.avatar[0] (FileList'ning birinchi fayli): async function onSubmit(data) { const file = data.avatar[0]; // File obyekti const formData = new FormData(); formData.append("avatar", file); formData.append("name", data.name); await fetch("/api/upload", { method: "POST", body: formData }); // multipart (5.x) } ZOD bilan fayl validatsiya (hajm/tur): avatar: z.instanceof(FileList) .refine((f) => f.length > 0, "Rasm tanlang") .refine((f) => f[0]?.size < 2_000_000, "2MB dan kichik bo'lsin") .refine((f) => f[0]?.type.startsWith("image/"), "Faqat rasm"), Fayl — JSON emas FormData (multipart/form-data) bilan yuboriladi (5.x backend) data.avatar — FileList (massiv emas); birinchi fayl — data.avatar[0]Fayl yuklash —
<input type="file">RHF bilan biroz o'zgacha, chunki fayl qiymati matn emas,FileListobyektidir.register("avatar")bilan ulaysiz — RHFe.target.filesni (FileList) saqlaydi. Submit'da faylnidata.avatar[0](FileListning birinchiFilei) sifatida olasiz. Yuborish — fayl JSON'da ketmaydi:FormDatayaratib (formData.append("avatar", file)+ boshqa maydonlar),fetch(..., { body: formData })bilan multipart/form-data so'rov yuborasiz (backend buni maxsus qabul qiladi — 5-QISM). Zod validatsiya — fayl hajmi va turiniz.instanceof(FileList)+.refinebilan tekshirasiz:f.length > 0(fayl tanlangan),f[0].size < 2MB(hajm chegarasi),f[0].type.startsWith("image/")(faqat rasm). Ikki nuqta: (1) fayl bo'lganda so'rovFormData(multipart), oddiy JSON emas; (2)data.avatar—FileList(bir nechta fayl bo'lishi mumkin —multipleatributi bilan), birinchisidata.avatar[0]. Rasm, hujjat, avatar yuklash — bu naqsh bilan qilinadi; ko'p fayl uchundata.filesbo'ylab aylanasiz.2.19. Ko'p bosqichli forma (wizard / multi-step)
textMULTI-STEP FORMA — bitta forma, bir nechta qadam (uzun forma bosqichlarga bo'linadi): function Wizard() { const [step, setStep] = useState(0); // joriy bosqich (state) const { register, handleSubmit, trigger, formState: { errors } } = useForm({ resolver: zodResolver(fullSchema), mode: "onBlur", }); async function next() { const fields = stepFields[step]; // shu bosqich maydonlari const ok = await trigger(fields); // FAQAT shu bosqichni validatsiyala if (ok) setStep((s) => s + 1); // valid keyingi qadam } return ( <form onSubmit={handleSubmit(onSubmit)}> {step === 0 && <input {...register("name")} />} {/* 1-qadam */} {step === 1 && <input {...register("address")} />} {/* 2-qadam */} {step === 2 && <button type="submit">Tasdiqlash</button>} {/* oxirgi */} {step > 0 && <button type="button" onClick={() => setStep(s => s-1)}>Orqaga</button>} {step < 2 && <button type="button" onClick={next}>Keyingi</button>} </form> ); } Bitta useForm — barcha bosqich (holat saqlanadi); step — useState trigger([fields]) — FAQAT joriy bosqichni tekshir ("Keyingi" uchun); submit — oxiridaKo'p bosqichli forma (wizard / multi-step) — uzun formani (masalan ro'yxatdan o'tish: shaxsiy manzil to'lov) bir nechta qadamga bo'lish (UX yaxshilanadi — foydalanuvchi bir vaqtda kam maydon ko'radi). Kalit g'oya: bitta
useFormbutun forma uchun (barcha qadam qiymatlari bir joyda saqlanadi — qadamlar orasida yo'qolmaydi), joriy qadam esa oddiyuseStateda.trigger— RHF'ning muhim vositasi:trigger(["name", "email"])faqat berilgan maydonlarni validatsiyalaydi (butun formani emas) vatrue/falseqaytaradi — "Keyingi" tugmasida joriy qadam maydonlarini tekshirib, valid bo'lsagina keyingi qadamga o'tasiz. To'liqhandleSubmitesa faqat oxirgi qadamda (butun sxema bo'yicha). Ikki nuqta: (1) bittauseForm— holat qadamlar bo'ylab saqlanadi (har qadamga alohida forma emas); (2)trigger(fields)— qisman validatsiya (qadam tugatilganda). Har qadam uchun alohida Zod sxema bo'lishi ham mumkin (.pick(...)bilan katta sxemadan bo'lak olib). Bu bob oxiridagi amaliy loyiha aynan shu naqshni qo'llaydi (9-bo'lim).2.20. Formik bilan taqqoslash va server action formalar
textRHF vs FORMIK (eski standart) — nega RHF g'olib: ┌──────────────┬─────────────────────────┬───────────────────────────┐ │ │ Formik │ React Hook Form │ ├──────────────┼─────────────────────────┼───────────────────────────┤ │ Yondashuv │ controlled (state) │ uncontrolled (ref — TEZ) │ │ Re-render │ ko'p (har harf) │ kam 2.2-bob │ │ Validatsiya │ Yup (yoki qo'lda) │ Zod (yoki built-in) │ │ Kod hajmi │ ko'proq │ kamroq (register) │ │ Holati (2026)│ eski loyihalar │ zamonaviy standart │ └──────────────┴─────────────────────────┴───────────────────────────┘ Yangi loyihada — RHF + Zod; Formik'ni faqat eski kodda uchratasiz SERVER ACTION FORMALAR (Next.js — 13-QISM): - Next.js Server Actions — forma to'g'ridan server funksiyasiga yuboriladi (client JS'siz ham) - useActionState / <form action={serverAction}> — RHF'siz "progressive enhancement" - RHF (client UX) + Zod (server'da qayta) birga ishlatiladi (13-QISM'da chuqur)Formik bilan taqqoslash — Formik RHF'dan oldingi eng mashhur forma kutubxonasi edi. Farqi: Formik controlled yondashuvga tayanadi (har maydon state'da — har harfda re-render, 2.2 muammosi), validatsiya uchun odatda Yup ishlatadi; RHF esa uncontrolled (ref — tez, kam re-render) va Zod bilan juftlashadi. RHF kamroq kod, yaxshiroq performance beradi — shuning uchun 2026 holatida yangi loyihalar deyarli har doim RHF + Zod tanlaydi; Formik'ni asosan eski (legacy) kod bazasida uchratasiz (migratsiya kerak bo'lganda foydali bilim). Server action formalar — Next.js (13-QISM) Server Actions bilan forma to'g'ridan server funksiyasiga yuborilishi mumkin (
<form action={serverAction}>,useActionState), hatto client JavaScript'siz ham ishlaydi (progressive enhancement). Amalda RHF (client tarafda darrov UX, jonli validatsiya) va Zod (server action ichida ma'lumotni qayta va xavfsiz tekshirish — 5.9, 14) birga ishlatiladi. Xulosa: forma kutubxonasi tanlovi — RHF + Zod (client-heavy SPA); Next.js Server Actions kontekstida ikkalasi birlashtiriladi (13-QISM'da to'liq). Formik — tarixiy kontekst va legacy migratsiya uchun bilib qo'yish foydali.2.21. Xato xabarlarini o'zbekcha va i18n
textXATO MATNLARI — Zod'da to'g'ridan o'zbekcha yozish (eng sodda): z.string().min(8, "Kamida 8 belgi").email("Email noto'g'ri") KO'P TILLI (i18n — 11.x/12.x) — matnni tarjima kalitiga bog'lash: z.string().min(8, t("errors.password.min")) // t() — tarjima funksiyasi // yoki xato KODINI ber, ko'rsatishda tarjima qil: {errors.password && <span>{t(`errors.${errors.password.type}`)}</span>} GLOBAL default (Zod errorMap) — barcha xato uchun bir joyda o'zbekcha: z.setErrorMap((issue, ctx) => { if (issue.code === "too_small") return { message: "Juda qisqa" }; return { message: ctx.defaultError }; }); Kichik loyiha — matnni to'g'ridan o'zbekcha; ko'p tilli — i18n kaliti (t()) Zod default xatolari inglizcha errorMap yoki har validatorda matn berXato xabarlarini o'zbekcha va i18n — Zod'ning standart (default) xato matnlari inglizcha ("String must contain at least 8 character(s)"), shuning uchun o'zbek tilidagi ilovada ularni almashtirish kerak. Uch usul: (1) eng sodda — har validatorga to'g'ridan o'zbekcha matn (
z.string().min(8, "Kamida 8 belgi")) — kichik loyiha uchun yetarli; (2) ko'p tilli (i18n) — matnni tarjima funksiyasiga bog'lash (z.string().min(8, t("errors.password.min"))yoki xato kodini berib, ko'rsatishdat()bilan tarjima qilish) — ilova bir nechta tilni qo'llasa (11-12-QISM i18n bilan); (3) global default —z.setErrorMap(...)bilan barcha Zod xatolari uchun bir joyda o'zbekcha matnlar (har validatorda takror yozmaslik). Ikki tamoyil: (1) kichik/bir tilli ilova — matnni to'g'ridan o'zbekcha yozing; (2) ko'p tilli ilova — xato matnini i18n tizimiga (tarjima kalitlari) bog'lang. Xato matnlari — foydalanuvchi ko'radigan yuz, shuning uchun ular tabiiy, aniq va foydalanuvchi tilida bo'lishi kerak (UX).
3. Sintaksis — tez ma'lumotnoma
textSETUP: npm i react-hook-form zod @hookform/resolvers FORMA 2.3-bob: const { register, handleSubmit, formState:{errors} } = useForm({ resolver: zodResolver(schema) }) REGISTER 2.3-bob: <input {...register("email")} /> SUBMIT 2.3-bob: <form onSubmit={handleSubmit(onSubmit)}> // onSubmit(data) ZOD 2.6-bob: z.object({ email: z.string().email(), age: z.number().min(18) }) + z.infer<typeof s> XATO 2.8-bob: {errors.email && <span>{errors.email.message}</span>} CONTROLLER 2.9-bob: <Controller name="x" control={control} render={({field}) => <Select {...field}/>} /> BOSHQARUV 2.10-bob: watch("x") | setValue("x",v) | getValues("x") | reset(data) DEFAULT 2.11-bob: useForm({ defaultValues: {...} }) | reset(apiData) // tahrir ASYNC 2.12-bob: isSubmitting | setError("email",{message}) | setError("root",{message}) ARRAY 2.13-bob: const { fields, append, remove } = useFieldArray({ control, name:"phones" }) useController 2.15-bob: const { field, fieldState } = useController({ name, control }) // reusable maydon TRIGGER 2.19-bob: await trigger(["field1","field2"]) // qisman validatsiya (multi-step) FAYL 2.18-bob: <input type="file" {...register("avatar")} /> // data.avatar[0] (FileList) ZOD ILG'OR 2.6-bob: .optional() | .nullable() | z.array(...) | .superRefine((d,ctx)=>ctx.addIssue({...}))
4. Batafsil kod namunalari
Misol 1 — Controlled og'rig'i 11.4-bob vs RHF (2.1, 2.3)
jsx// CONTROLLED 11.4-bob — har maydon useState, har harf re-render: function ControlledForm() { const [name, setName] = useState(""); const [email, setEmail] = useState(""); // ... har maydon, har onChange, validatsiya qo'lda return ( <form> <input value={name} onChange={e => setName(e.target.value)} /> <input value={email} onChange={e => setEmail(e.target.value)} /> </form> ); } // RHF — register, useState yo'q, re-render kam: function RHFForm() { const { register, handleSubmit } = useForm(); return ( <form onSubmit={handleSubmit(data => console.log(data))}> <input {...register("name")} /> {/* useState yo'q */} <input {...register("email")} /> <button>Yuborish</button> </form> ); } // RHF — kam kod + har harfda re-render yo'q (uncontrolled — 2.2)Misol 2 — Asosiy RHF (register, handleSubmit, errors — 2.3, 2.4)
jsximport { useForm } from "react-hook-form"; function ContactForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm(); const onSubmit = async (data) => { await new Promise(r => setTimeout(r, 1000)); // soxta async (server) console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("name", { required: "Ism majburiy" })} placeholder="Ism" /> {errors.name && <span className="error">{errors.name.message}</span>} <textarea {...register("message", { required: "Xabar majburiy" })} /> {errors.message && <span className="error">{errors.message.message}</span>} <button disabled={isSubmitting}>{isSubmitting ? "Yuborilmoqda..." : "Yuborish"}</button> </form> ); }Misol 3 — Built-in validatsiya qoidalari (2.5)
jsxfunction SignupForm() { const { register, handleSubmit, formState: { errors }, watch } = useForm(); const password = watch("password"); // mos kelish uchun kuzat (2.10) return ( <form onSubmit={handleSubmit(d => console.log(d))}> <input {...register("email", { required: "Email majburiy", pattern: { value: /^\S+@\S+\.\S+$/, message: "Email noto'g'ri" }, })} /> {errors.email && <span>{errors.email.message}</span>} <input type="password" {...register("password", { required: "Parol majburiy", minLength: { value: 8, message: "Kamida 8 belgi" }, })} /> {errors.password && <span>{errors.password.message}</span>} <input type="password" {...register("confirm", { validate: (v) => v === password || "Parollar mos emas", // custom 2.5-bob })} /> {errors.confirm && <span>{errors.confirm.message}</span>} <button>Ro'yxatdan o'tish</button> </form> ); }Misol 4 — Zod sxema va type inference (2.6)
typescriptimport { z } from "zod"; export const registerSchema = z.object({ name: z.string().min(2, "Kamida 2 belgi"), email: z.string().email("Email noto'g'ri"), age: z.coerce.number().min(18, "18 dan katta bo'lsin"), // coerce — string number password: z.string().min(8, "Kamida 8 belgi"), role: z.enum(["user", "admin"]), terms: z.literal(true, { errorMap: () => ({ message: "Shartlarni qabul qiling" }) }), }); // Tur — sxemadan AVTOMATIK (qo'lda interface yozish yo'q): export type RegisterData = z.infer<typeof registerSchema>; // { name: string; email: string; age: number; password: string; role: "user"|"admin"; terms: true } // Bir sxema = qoidalar + tur (single source — 5.9; frontend + backend bir xil)Misol 5 — zodResolver bilan to'liq forma (2.7, 2.8)
tsximport { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { registerSchema, RegisterData } from "./schema"; function RegisterForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<RegisterData>({ resolver: zodResolver(registerSchema), // Zod ulandi mode: "onBlur", // fokus ketganda tekshir (2.8) }); const onSubmit = (data: RegisterData) => console.log(data); // type-safe return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label htmlFor="email">Email</label> <input id="email" {...register("email")} aria-invalid={!!errors.email} /> {errors.email && <span role="alert" className="error">{errors.email.message}</span>} </div> <div> <label htmlFor="password">Parol</label> <input id="password" type="password" {...register("password")} /> {errors.password && <span role="alert">{errors.password.message}</span>} </div> <button disabled={isSubmitting}>Ro'yxatdan o'tish</button> </form> ); } // Validatsiya Zod'da (markazlashgan, type-safe), xatolar avtomatik errors'da (2.7)Misol 6 — Controller (UI kutubxona / custom select — 2.9)
tsximport { Controller, useForm } from "react-hook-form"; import Select from "react-select"; // controlled UI kutubxona function ProfileForm() { const { control, handleSubmit } = useForm(); const options = [{ value: "uz", label: "O'zbekiston" }, { value: "kz", label: "Qozog'iston" }]; return ( <form onSubmit={handleSubmit(d => console.log(d))}> <Controller name="country" control={control} rules={{ required: "Davlat tanlang" }} render={({ field, fieldState }) => ( <> <Select {...field} options={options} /> {/* field: value/onChange (controlled) */} {fieldState.error && <span>{fieldState.error.message}</span>} </> )} /> <button>Saqlash</button> </form> ); } // react-select — controlled (register ishlamaydi) Controller ko'prik (2.9)Misol 7 — watch bilan shartli maydon (2.10)
jsxfunction CheckoutForm() { const { register, handleSubmit, watch } = useForm({ defaultValues: { delivery: "pickup" } }); const delivery = watch("delivery"); // o'zgarganda re-render (shartli UI) return ( <form onSubmit={handleSubmit(d => console.log(d))}> <select {...register("delivery")}> <option value="pickup">Olib ketish</option> <option value="courier">Kuryer</option> </select> {delivery === "courier" && ( // faqat kuryer tanlansa (watch — 2.10) <input {...register("address", { required: true })} placeholder="Manzil" /> )} <button>Buyurtma</button> </form> ); }Misol 8 — Tahrirlash formasi: default + reset (2.11)
tsxfunction EditProfile({ userId }) { const { register, handleSubmit, reset } = useForm(); const { data: user, loading } = useFetch(`/api/users/${userId}`); // 11.7 useEffect(() => { if (user) reset(user); // ma'lumot kelgach formani to'ldir (2.11) }, [user, reset]); if (loading) return <Spinner />; return ( <form onSubmit={handleSubmit(d => console.log("Saqlash:", d))}> <input {...register("name")} /> <input {...register("email")} /> <button>Saqlash</button> </form> ); } // Tahrir formasi standart naqsh: fetch reset(data) (defaultValues async keladi — 2.11)Misol 9 — Async submit va server xatolari (2.12)
tsxfunction RegisterForm() { const { register, handleSubmit, setError, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(registerSchema) }); async function onSubmit(data) { try { await api.register(data); // server (isSubmitting avtomatik) } catch (err) { if (err.code === "EMAIL_TAKEN") { setError("email", { message: "Bu email band" }); // maydon xatosi (server'dan — 2.12) } else { setError("root", { message: "Xatolik. Qayta urinib ko'ring." }); // umumiy } } } return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("email")} /> {errors.email && <span>{errors.email.message}</span>} {errors.root && <p className="error">{errors.root.message}</p>} {/* umumiy xato */} <button disabled={isSubmitting}>{isSubmitting ? "..." : "Ro'yxat"}</button> </form> ); } // Server xatosi (email band) setError; frontend Zod tuta olmaydi (faqat server biladi — 2.12)Misol 10 — useFieldArray (dinamik telefon raqamlari — 2.13)
tsximport { useForm, useFieldArray } from "react-hook-form"; function PhonesForm() { const { register, control, handleSubmit } = useForm({ defaultValues: { phones: [{ number: "" }] }, }); const { fields, append, remove } = useFieldArray({ control, name: "phones" }); return ( <form onSubmit={handleSubmit(d => console.log(d))}> {fields.map((field, index) => ( <div key={field.id}> {/* RHF id — barqaror (11.4: 2.13) */} <input {...register(`phones.${index}.number`)} placeholder="Telefon" /> {fields.length > 1 && ( <button type="button" onClick={() => remove(index)}>O'chir</button> )} </div> ))} <button type="button" onClick={() => append({ number: "" })}>+ Raqam qo'sh</button> <button type="submit">Saqlash</button> </form> ); } // append/remove — dinamik; key={field.id} (index emas — 2.13); type="button" (submit qilmasin)Misol 11 — Zod ilg'or: refine (parol mosligi — 2.6)
typescriptconst passwordSchema = z.object({ password: z.string().min(8, "Kamida 8 belgi") .regex(/[A-Z]/, "Bosh harf kerak") .regex(/[0-9]/, "Raqam kerak"), confirm: z.string(), }).refine((data) => data.password === data.confirm, { // ikki maydonni solishtirish message: "Parollar mos emas", path: ["confirm"], // xato qaysi maydonda ko'rinsin }); // refine — bir nechta maydonga bog'liq qoida (built-in qila olmaydi — 2.6) // path — xato confirm maydoniga biriktiriladi (errors.confirm)Misol 12 — Zod transform va coerce (2.6)
typescriptconst schema = z.object({ age: z.coerce.number().int().positive(), // "25" (string) 25 (number) email: z.string().email().toLowerCase().trim(), // tozala + kichik harf (transform) tags: z.string().transform(s => s.split(",").map(t => t.trim())), // "a,b,c" ["a","b","c"] birthDate: z.coerce.date(), // string Date }); // coerce/transform — kirishni tozalab/o'girib beradi (input string kerakli tur)Misol 13 — To'liq a11y forma (2.14)
tsxfunction AccessibleForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(schema), mode: "onBlur" }); return ( <form onSubmit={handleSubmit(d => console.log(d))} noValidate> <div className="field"> <label htmlFor="email">Email manzil</label> <input id="email" type="email" {...register("email")} aria-invalid={errors.email ? "true" : "false"} // a11y holati aria-describedby={errors.email ? "email-err" : undefined} /> {errors.email && ( <span id="email-err" role="alert" className="error">{errors.email.message}</span> )} </div> <button type="submit" disabled={isSubmitting}>Yuborish</button> </form> ); } // label + aria-invalid + aria-describedby + role="alert" — ekran o'quvchi to'liq tushunadi (2.14, 1.9)Misol 14 — To'liq ro'yxatdan o'tish formasi (hammasi birga)
tsxconst schema = z.object({ name: z.string().min(2, "Kamida 2 belgi"), email: z.string().email("Email noto'g'ri"), password: z.string().min(8, "Kamida 8 belgi"), confirm: z.string(), age: z.coerce.number().min(18, "18+"), role: z.enum(["user", "seller"]), terms: z.literal(true, { errorMap: () => ({ message: "Shartlarni qabul qiling" }) }), }).refine(d => d.password === d.confirm, { message: "Parollar mos emas", path: ["confirm"] }); type FormData = z.infer<typeof schema>; function FullRegisterForm() { const { register, handleSubmit, setError, watch, formState: { errors, isSubmitting } } = useForm<FormData>({ resolver: zodResolver(schema), mode: "onBlur" }); const role = watch("role"); async function onSubmit(data: FormData) { try { await api.register(data); } catch (err) { setError("root", { message: "Ro'yxatdan o'tib bo'lmadi" }); } } return ( <form onSubmit={handleSubmit(onSubmit)} noValidate> <input {...register("name")} placeholder="Ism" /> {errors.name && <span>{errors.name.message}</span>} <input {...register("email")} placeholder="Email" /> {errors.email && <span>{errors.email.message}</span>} <input type="password" {...register("password")} placeholder="Parol" /> {errors.password && <span>{errors.password.message}</span>} <input type="password" {...register("confirm")} placeholder="Parolni takrorlang" /> {errors.confirm && <span>{errors.confirm.message}</span>} <input type="number" {...register("age")} placeholder="Yosh" /> {errors.age && <span>{errors.age.message}</span>} <select {...register("role")}> <option value="user">Xaridor</option> <option value="seller">Sotuvchi</option> </select> {role === "seller" && <input {...register("shopName")} placeholder="Do'kon nomi" />} <label><input type="checkbox" {...register("terms")} /> Shartlarga roziman</label> {errors.terms && <span>{errors.terms.message}</span>} {errors.root && <p className="error">{errors.root.message}</p>} <button disabled={isSubmitting}>{isSubmitting ? "Yuborilmoqda..." : "Ro'yxatdan o'tish"}</button> </form> ); } // Zod (qoida+tur) + RHF (boshqaruv) + a11y + async + watch — to'liq production formaMisol 15 — Reusable maydon komponenti + useController (2.15, 2.16)
tsximport { useController, Control, useForm } from "react-hook-form"; // 1) Oddiy input uchun — register uzatiladi 2.16-bob: function Field({ label, name, register, error, type = "text" }) { return ( <div className="field"> <label htmlFor={name}>{label}</label> <input id={name} type={type} {...register(name)} aria-invalid={error ? "true" : "false"} /> {error && <span role="alert" className="error">{error.message}</span>} </div> ); } // 2) Controlled UI-kutubxona maydoni uchun — useController 2.15-bob: function SelectField({ name, control, label, options }) { const { field, fieldState } = useController({ name, control }); return ( <div className="field"> <label>{label}</label> <select {...field}> {options.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)} </select> {fieldState.error && <span role="alert">{fieldState.error.message}</span>} </div> ); } function Form() { const { register, control, handleSubmit, formState: { errors } } = useForm(); return ( <form onSubmit={handleSubmit((d) => console.log(d))}> <Field label="Ism" name="name" register={register} error={errors.name} /> <SelectField name="role" control={control} label="Rol" options={[{ value: "user", label: "Xaridor" }, { value: "admin", label: "Admin" }]} /> <button>Saqlash</button> </form> ); } // Field (register) + SelectField (useController) — takror kodsiz, a11y markazlashgan (2.16)Misol 16 — Ko'p bosqichli forma: trigger bilan (2.19)
tsxconst fullSchema = z.object({ name: z.string().min(2, "Kamida 2 belgi"), email: z.string().email("Email noto'g'ri"), city: z.string().min(1, "Shahar majburiy"), card: z.string().length(16, "16 raqam"), }); type WizardData = z.infer<typeof fullSchema>; const stepFields: Record<number, (keyof WizardData)[]> = { 0: ["name", "email"], // 1-qadam maydonlari 1: ["city"], // 2-qadam 2: ["card"], // 3-qadam }; function Wizard() { const [step, setStep] = useState(0); const { register, handleSubmit, trigger, formState: { errors, isSubmitting } } = useForm<WizardData>({ resolver: zodResolver(fullSchema), mode: "onBlur" }); async function next() { const ok = await trigger(stepFields[step]); // FAQAT joriy qadamni tekshir (2.19) if (ok) setStep((s) => s + 1); } const onSubmit = (data: WizardData) => console.log("Yakuniy:", data); return ( <form onSubmit={handleSubmit(onSubmit)} noValidate> {step === 0 && ( <> <input {...register("name")} placeholder="Ism" /> {errors.name && <span role="alert">{errors.name.message}</span>} <input {...register("email")} placeholder="Email" /> {errors.email && <span role="alert">{errors.email.message}</span>} </> )} {step === 1 && ( <> <input {...register("city")} placeholder="Shahar" /> {errors.city && <span role="alert">{errors.city.message}</span>} </> )} {step === 2 && ( <> <input {...register("card")} placeholder="Karta raqami" /> {errors.card && <span role="alert">{errors.card.message}</span>} </> )} <div> {step > 0 && <button type="button" onClick={() => setStep((s) => s - 1)}>Orqaga</button>} {step < 2 ? <button type="button" onClick={next}>Keyingi</button> : <button type="submit" disabled={isSubmitting}>Tasdiqlash</button>} </div> </form> ); } // Bitta useForm (holat saqlanadi); trigger(qadam maydonlari) — qisman validatsiya (2.19)Misol 17 — superRefine, optional/nullable, array (ilg'or Zod — 2.6)
typescriptconst schema = z.object({ username: z.string().min(3), email: z.string().email(), // optional — bo'lishi shart emas (undefined ruxsat): bio: z.string().max(200).optional(), // nullable — null bo'lishi mumkin (masalan DB'dan): middleName: z.string().nullable(), // array — obyektlar massivi (useFieldArray bilan — 2.13): skills: z.array(z.object({ name: z.string().min(1) })).min(1, "Kamida 1 ta ko'nikma"), password: z.string().min(8), confirm: z.string(), promoCode: z.string().optional(), }) // superRefine — bir NECHTA maydonga bog'liq murakkab qoida (refine'dan kuchliroq): .superRefine((data, ctx) => { if (data.password !== data.confirm) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Parollar mos emas", path: ["confirm"], // xato confirm maydoniga }); } if (data.promoCode && data.promoCode.length !== 6) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Promo-kod 6 belgi bo'lsin", path: ["promoCode"], }); } }); // superRefine — bir nechta shartli xato qo'shish (refine — bitta; superRefine — ctx bilan ko'p) // optional (undefined) / nullable (null) — farqli; array — dinamik ro'yxat validatsiyasi
5. To'g'ri va noto'g'ri holatlar
1) Katta forma
text15 ta useState + qo'lda onChange/validatsiya (sekin, ko'p kod — 2.1) React Hook Form (register — kam re-render, kam kod — 2.3)2) Validatsiya
texthar inputda qo'lda regex/shart (takror, xatoga moyil) Zod sxema (markazlashgan, type-safe, backend bilan bir xil — 2.6)3) UI kutubxona maydoni
text<Select {...register("x")} /> (controlled komponentga register ishlamaydi — 2.9) <Controller render={({field}) => <Select {...field}/>} />4) Ro'yxat key (useFieldArray)
textkey={index} (ro'yxat o'zgarsa buziladi — 11.4: 2.13) key={field.id} (RHF beradi — barqaror)5) Async submit
textqo'lda loading state + tugma disable unutish (ikki marta yuborish) isSubmitting (avtomatik) + disabled={isSubmitting} (2.12)6) Server xatosi
textserver xatosini e'tiborsizlik (foydalanuvchi bilmaydi) setError("field"/"root", {message}) — formaga qaytar (2.12)
6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Input "uncontrolled to controlled" ogohlantirishi
Sababi:
defaultValuesberilmagan (qiymatundefineddan boshlandi — 2.11, 11.4). Yechimi:useForm({ defaultValues: {...} })(har maydonga boshlang'ich qiymat).Xato 2 —
errors.x.messageundefined (xato ko'rinmaydi)Sababi: validatsiya qoidasiga
messageberilmagan, yoki Zod xato matni yo'q. Yechimi: qoidaga matn ber (required: "Majburiy",z.string().min(8, "...")— 2.5, 2.6).Xato 3 — UI kutubxona maydoni RHF bilan ishlamaydi
Sababi: controlled komponentga
registerishlatildi 2.9-bob. Yechimi:<Controller>bilan o'ra (Misol 6).Xato 4 —
handleSubmitishlamaydi (onSubmit chaqirilmaydi)Sababi: validatsiya o'tmadi (xato bor —
handleSubmitfaqat valid bo'lsa chaqiradi), yoki tugmatype="button". Yechimi:errorsni tekshir (qaysi maydon xato); tugmatype="submit"(2.3, 2.14).Xato 5 — useFieldArray'da maydonlar aralashadi/yo'qoladi
Sababi:
key={index}ishlatildi (2.13, 11.4: 2.13). Yechimi:key={field.id}(RHF bergan barqaror id).Xato 6 — Zod number maydoni "expected number, received string"
Sababi: input qiymati string, Zod number kutadi 11.4-bob. Yechimi:
z.coerce.number()(avtomatik o'giradi) yokiregister("age", { valueAsNumber: true })(2.5, 2.6).Xato 7 — Tahrirlash formasi ma'lumot bilan to'lmaydi
Sababi: ma'lumot async keldi,
defaultValuesallaqachon bo'sh o'rnatilgan 2.11-bob. Yechimi:useEffectdareset(data)(ma'lumot kelgach — Misol 8).
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- Controlled inputs (11.4: 2.11): RHF ularning og'rig'ini (re-render, kod) hal qiladi.
- Zod 5.9-bob: bir xil sxema backend (NestJS/Express) va frontend formada — full-stack DRY.
- TypeScript (11.14, 7-QISM):
z.infer— sxemadan tur; type-safe forma.- useRef (11.5: 2.12): RHF uncontrolled yondashuv ref'larga tayanadi 2.2-bob.
- Routing 11.9-bob: submit'dan keyin
navigate; tahrir formasiuseParamsbilan id.- Data fetching 12.4-bob: tahrir formasi ma'lumoti (Query); submit (mutation).
- Auth 5.16-bob: login/register formasi — RHF + Zod + server xatolari 2.12-bob.
- a11y 1.9-bob: label, aria — forma har kim uchun ishlaydi 2.14-bob.
- Server Actions (13-QISM): Next.js'da forma to'g'ridan serverga; RHF (client UX) + Zod (server qayta tekshiruv) birga 2.20-bob.
- Fayl yuklash (5.x):
type="file"+ FormData (multipart) — rasm/hujjat yuklash 2.18-bob.- i18n (11/12-QISM): Zod xato matnlarini tarjima kalitlariga bog'lash (
t(),errorMap— 2.21).
8. Eng yaxshi amaliyotlar (best practices)
- RHF katta formaga (controlled o'rniga — kam re-render, kam kod — 2.2).
- Zod sxema validatsiyaga (markazlashgan, type-safe, backend bilan bir xil — 2.6, 5.9).
zodResolver+z.infer(sxema qoida + tur, type-safe submit — 2.7).ControllerUI kutubxonaga (oddiy input — register — 2.9).mode: "onBlur"(yumshoq xato UX — 2.8).isSubmittingbilan disable (ikki marta yuborish yo'q — 2.12).- Server xatolarini
setError(frontend tuta olmaganni — 2.12).defaultValues(controlled warning'dan saqlaydi; tahrir —reset— 2.11).key={field.id}useFieldArray'da (index emas — 2.13).- a11y to'liq (label, aria-invalid, aria-describedby, role=alert — 2.14, 1.9).
useControllerreusable maydonga (Controllerbir martalik; hook — dizayn tizimi — 2.15, 2.16).- Async validatsiya
onBlur/debounce bilan (har harfda server so'rovi yo'q — 2.17).- Multi-step formada
trigger([fields])(qisman validatsiya, bittauseForm— 2.19).- Fayl —
FormData(multipart), Zod bilan hajm/tur validatsiya 2.18-bob.
9. Amaliy loyiha: "Ko'p Bosqichli Ro'yxatdan O'tish (Multi-step Form)"
RHF + Zod bilan murakkab, bosqichli, validatsiyali, server'ga ulangan forma.
Maqsad
3 bosqichli ro'yxatdan o'tish formasi (shaxsiy ma'lumot manzil tasdiqlash) — har bosqich validatsiyalangan, dinamik maydonlar va server xatolari bilan.
Talablar (requirements)
- RHF setup:
useForm+zodResolver, TypeScript bilan (z.infer— Misol 5).- Zod sxema: har bosqich uchun sxema; parol mosligi (
refine— Misol 11),coerce(Misol 12).- Bosqichlar: 3 qadam, har biri o'z maydonlari; "Keyingi" faqat joriy bosqich valid bo'lsa (
trigger/isValid).- Xato ko'rsatish: field-level,
mode: "onBlur", a11y bilan (Misol 13, 2.8, 2.14).- watch: shartli maydon (masalan "sotuvchi" tanlansa do'kon nomi — Misol 7).
- useFieldArray: dinamik maydonlar (masalan bir nechta telefon yoki manzil — Misol 10).
- Controller: kamida bitta UI kutubxona maydoni (select/date — Misol 6).
- Async submit: server'ga yuborish,
isSubmitting, server xatolarisetError(Misol 9).- Tahrirlash rejimi (bonus): mavjud ma'lumotni
resetbilan to'ldirish (Misol 8).- a11y: to'liq label/aria, klaviatura bilan ishlash (Misol 13, 2.14).
Maslahatlar (hint)
- Bosqichli formada bitta
useForm, bosqichniuseStateda sakla; "Keyingi"datrigger(["field1","field2"])bilan joriy bosqichni validatsiyala.- Zod sxemani backend bilan ulashish uchun alohida faylda sakla (5.9 — bir manba).
z.coerce.number()— input string'ni number qiladi (Xato 6).- UI kutubxona (react-select) maydoni —
Controller(register emas — Misol 6).- Server xatosini (email band)
setErrorbilan formaga qaytar (Misol 9).- a11y'ni boshidan qo'sh (label/aria — keyin qo'shish qiyin — Misol 13).
"Tayyor" mezonlari (acceptance criteria)
- RHF + Zod + zodResolver, type-safe (
z.infer).- Har bosqich validatsiyalangan (keyingiga o'tish uchun valid kerak).
- Parol mosligi
refinebilan tekshiriladi.- Shartli maydon
watchbilan ishlaydi.- Dinamik maydonlar
useFieldArray(key={field.id}).- UI kutubxona maydoni
Controllerbilan.- Async submit + isSubmitting + server xatolari
setError.- To'liq a11y (label, aria, role=alert).
- Forma tez (katta bo'lsa ham re-render kam — uncontrolled).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda zamonaviy React forma standartini chuqur o'rgandik:
- Forma muammosi (controlled ko'lamda — 2.1); controlled vs uncontrolled (RHF falsafasi — 2.2); RHF asoslari (register/handleSubmit — 2.3); formState 2.4-bob; built-in validatsiya 2.5-bob.
- Zod (sxema + type inference — 2.6); zodResolver (RHF + Zod — 2.7); xato ko'rsatish 2.8-bob; Controller (UI kutubxona — 2.9); watch/setValue/reset 2.10-bob.
- Default values/tahrirlash 2.11-bob; async + server xatolari 2.12-bob; useFieldArray (dinamik — 2.13); a11y va best practices 2.14-bob.
- useController (reusable maydon — 2.15); qayta ishlatiladigan komponent 2.16-bob; async validatsiya + debounce 2.17-bob; fayl yuklash 2.18-bob; ko'p bosqichli forma 2.19-bob; Formik taqqoslash va server action 2.20-bob; xato matnlari o'zbekcha/i18n 2.21-bob.
Endi siz har qanday formani — login, ro'yxat, checkout, tahrirlash, ko'p bosqichli — samarali (kam re-render), type-safe (Zod + TypeScript), validatsiyali va a11y bilan qura olasiz. Bu — har real ilovaning eng ko'p uchraydigan qismi, va siz uni professional darajada bilasiz.
Keyingi bob —
11.11-bob: Performance optimizatsiya (memo, re-render). 11.6'damemo/useMemo/useCallbackni o'rgandik — endi ularni performance rakursidan birlashtirib, React ilovasini tezlashtirish san'atini o'rganamiz. Re-render qachon va nega bo'ladi, keraksiz re-render'larni qanday topish (React DevTools Profiler),React.memo/useMemo/useCallbackni qachon ishlatish, virtualizatsiya (uzun ro'yxatlar — react-window), va React 19 React Compilerning ta'siri. Eng muhimi — optimizatsiyani o'lchovga asoslanib qilish (11.6: 2.13 davomi).
Foydalanilgan rasmiy/ishonchli manbalar
- React Hook Form rasmiy hujjati (react-hook-form.com) —
useForm,register,handleSubmit,formState,Controller,useController,useFieldArray,setError,trigger,reset,watch, validatsiya rejimlari (mode).- Zod rasmiy hujjati (zod.dev) — sxema,
infer,refine/superRefine,coerce,transform,optional/nullable,array,enum,errorMap, xato xabarlari.@hookform/resolvers—zodResolver(RHF Zod ko'prigi); shuningdek Yup/Valibot resolverlari.- React rasmiy hujjati (react.dev) — controlled vs uncontrolled inputlar,
<form>,useState,useRef.- WAI-ARIA Authoring Practices — forma a11y (
<label>,aria-invalid,aria-describedby,role="alert").- MDN Web Docs —
FormData,<input type="file">,multipart/form-data, HTML forma elementlari.- Next.js rasmiy hujjati (nextjs.org) — Server Actions,
useActionState(server action formalar — 13-QISM).Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!