WisarWisar
Dasturlash kitobi/11-QISM — React50 daqiqa

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

text
  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 qilinadi

Forma 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 maydonda value/onChange (takror), validatsiya holati (errors), va har qoida (email to'g'rimi, parol kuchlimi, parollar mos) qo'lda yoziladi. Muammolar: (1) ko'p useState (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

text
  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 (onChange setState), 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")} />useState yo'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 maydonga useState/onChange o'rniga bitta register. 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

text
  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: u name, ref, va onChange/onBlur hodisalarini 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'tsa onSubmit(data)ni chaqiradi (databarcha maydon qiymatlari obyekti — qo'lda yig'ish yo'q), agar xato bo'lsa onSubmitni chaqirmaydi. Ikki muhim nuqta: (1) {...register("name")} — RHF'ning sirli qismi: u input'ga name/ref/hodisalarni spread qiladi (shuning uchun useState/onChange qo'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...)

text
  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 paytida true — 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) isSubmitting ayniqsa 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

text
  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'giradi

Built-in validatsiya — RHF'ning register ichida 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)

text
  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'lda interface yozish 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

text
  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/resolvers paketidan). 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'lsa onSubmit(data) chaqiriladi, aks holda xatolar formState.errorsga (Zod xato matnlari bilan) tushadi. Type-safe: type FormData = z.infer<typeof schema> bilan, onSubmit(data: FormData)data to'liq turlangan (TypeScript autocomplete, xatoni compile paytida tutadi). Bu — zamonaviy React forma'sining to'liq naqshi: Zod sxema (qoida + tur) zodResolver RHF (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 yerda resolver 11.9'dagi React Router'ning loader/resolveridan butunlay boshqa (nom o'xshashligi tasodif).

2.8. Xatolarni ko'rsatish — field-level errors

text
  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'rsatishformState.errors obyektidan maydon xatolari ko'rsatiladi. Eng oddiy: {errors.email && <span>{errors.email.message}</span>}errors.email bor bo'lsa, uning messageini (Zod yoki built-in qoidadan — 2.6, 2.5) chiqaradi. Toza naqsh (a11y bilan — 2.14): <label> + <input> + xato <span>, va aria-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 (mode opsiyasi): 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

text
  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'ning register'i oddiy <input>/<select> bilan ishlaydi (uncontrolled — ref orqali — 2.2), lekin UI kutubxona komponentlari (MUI Select, react-select, DatePicker, rich text editor) controlled (ular value/onChange kutadi, DOM ref'ni to'g'ridan bermaydi) — shuning uchun register ularda ishlamaydi. Yechim<Controller>: u name, control (useForm()dan), va render funksiyasini oladi; render field obyektini 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 komponent Controller; (2) controluseForm() qaytaradigan obyekt, uni Controllerga berasiz. Amalda formaning ko'p qismi register, faqat UI kutubxona maydonlari (sana tanlagich, ko'p tanlovli select) Controller bilan bo'ladi. Bu — RHF'ni har qanday komponent bilan ishlatishning kaliti.

2.10. watch, setValue, getValues, reset

text
  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 — masalan accountType === "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) watch re-render keltiradi (shartli UI uchun kerak), getValues keltirmaydi (faqat bir martalik o'qish — tezroq, kuzatish kerak bo'lmaganda); (2) reset ikki 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

text
  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. defaultValuesuseForm({ defaultValues: {...} }) bilan formaning boshlang'ich qiymatlari belgilanadi. Yaratish formasi: bo'sh default ({ name: "", email: "" }) — bu shuningdek "uncontrolled controlled" ogohlantirishini oldini oladi (11.4 — undefined qiymatdan boshlama). Tahrirlash formasi (mavjud ma'lumotni o'zgartirish): ma'lumot serverdan async keladi, shuning uchun defaultValues boshlang'ich render'da hali bo'sh — yechim: ma'lumot kelgach useEffect ichida reset(user) bilan formani to'ldirasiz 2.10-bob. Yoki RHF v7+ da defaultValuesga async funksiya berish mumkin (async () => fetch(...)). Ikki muhim nuqta: (1) defaultValues — formani to'g'ri boshlaydi (har maydonga boshlang'ich qiymat — undefined warning'dan saqlaydi); (2) tahrirlash formasining standart naqshi: ma'lumotni useFetch 11.7-bob yoki Query 12.4-bob bilan ol useEffectda reset(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

text
  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. onSubmit async bo'lganda (API so'rovi), RHF isSubmittingni avtomatik kuzatadi (qo'lda loading state kerak emas — submit boshlanganda true, tugaganda false). Naqsh: try ichida await api.register(data) muvaffaqiyatli bo'lsa reset() (tozalash) + navigate 11.9-bob; catch ichida server xatolarini boshqarish. Server xatolari — frontend validatsiya tuta olmaydigan xatolar (masalan "bu email allaqachon band", "parol noto'g'ri") — ular faqat serverdan keladi. Bularni setError bilan formaga qo'shasiz: setError("email", { message: "Email band" }) (aniq maydonga) yoki setError("root", { message: "..." }) (umumiy forma xatosi — errors.root orqali ko'rsatiladi). Tugma disabled={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), setError server xatosini formaga "qaytaradi"; (2) isSubmitting async submit UX'ini (loading tugma) avtomatik boshqaradi. Bu — real, serverga ulangan formaning to'liq naqshi.

2.13. useFieldArray — dinamik maydonlar

text
  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 barqaror id), append (oxiriga qo'shish), remove(index) (o'chirish), shuningdek insert/move/swap/prepend. Render: fields.map((field, index) => ...) — har element uchun register(\phones.${index}.number`)(ichma-ich maydon nomi — massiv ichidagi obyekt), va

. Ikki kritik nuqta: (1) **key={field.id}** — RHF bergan barqaror idni ishlat, **key={index}emas** (11.4: 2.13 — ro'yxat o'zgarganda index buziladi, RHF'ningfield.id esa 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

text
  FORMA 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"> (yoki aria-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); isSubmitting bilan tugmani disable (ikki marta yuborish yo'q — 2.12); server xatolarini setError bilan 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)

text
  useController — 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

useControllerControllerning hook ko'rinishi: bir xil ish (RHF'ni controlled komponentga ulash — 2.9), lekin render prop o'rniga to'g'ridan hook chaqiriladi. useController({ name, control }) ikki narsa qaytaradi: field (value, onChange, onBlur, ref, name — controlled komponentga {...field} bilan beriladi) va fieldState (error, isDirty, isTouched, invalid — o'sha maydon holati). Qachon Controller vs useController: 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/onChange kutadi, DOM ref bermaydi), shuning uchun register ishlamaydi; ularni Controller yoki useController bilan o'raysiz — field.value/field.onChange orqali RHF bilan sinxronlanadi. Amaliy tanlov: oddiy <input> register; bir martalik UI-kutubxona maydoni Controller; qayta ishlatiladigan maydon komponenti useController. Bu uchtasi — RHF'ni har qanday inputga ulashning to'liq to'plami.

2.16. Qayta ishlatiladigan forma komponenti

text
  MUAMMO: 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, error proplarini uzatish. Ikki yondashuv: (1) oddiy input uchun — register funksiyasini va errorni prop sifatida uzatasiz (yuqoridagi <Field>); (2) controlled UI-kutubxona maydoni uchun — komponent ichida useController 2.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

text
  ASYNC 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 UX

Async 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 validate funksiyasi async bo'lishi mumkin — ichida fetch qilib, true (valid) yoki xato matni qaytaradi; (2) Zod .refine ham async funksiya qabul qiladi (refine(async v => await check(v), "...")) — zodResolver buni 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 uchun watch + 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 yoki onBlur majburiy — 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)

text
  FAYL 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, FileList obyektidir. register("avatar") bilan ulaysiz — RHF e.target.filesni (FileList) saqlaydi. Submit'da faylni data.avatar[0] (FileListning birinchi Filei) sifatida olasiz. Yuborish — fayl JSON'da ketmaydi: FormData yaratib (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 turini z.instanceof(FileList) + .refine bilan 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'rov FormData (multipart), oddiy JSON emas; (2) data.avatarFileList (bir nechta fayl bo'lishi mumkin — multiple atributi bilan), birinchisi data.avatar[0]. Rasm, hujjat, avatar yuklash — bu naqsh bilan qilinadi; ko'p fayl uchun data.files bo'ylab aylanasiz.

2.19. Ko'p bosqichli forma (wizard / multi-step)

text
  MULTI-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 — oxirida

Ko'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 useForm butun forma uchun (barcha qadam qiymatlari bir joyda saqlanadi — qadamlar orasida yo'qolmaydi), joriy qadam esa oddiy useStateda. trigger — RHF'ning muhim vositasi: trigger(["name", "email"]) faqat berilgan maydonlarni validatsiyalaydi (butun formani emas) va true/false qaytaradi — "Keyingi" tugmasida joriy qadam maydonlarini tekshirib, valid bo'lsagina keyingi qadamga o'tasiz. To'liq handleSubmit esa faqat oxirgi qadamda (butun sxema bo'yicha). Ikki nuqta: (1) bitta useForm — 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

text
  RHF 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

text
  XATO 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 ber

Xato 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'rsatishda t() bilan tarjima qilish) — ilova bir nechta tilni qo'llasa (11-12-QISM i18n bilan); (3) global defaultz.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

text
SETUP:             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)

jsx
import { 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)

jsx
function 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)

typescript
import { 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)

tsx
import { 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)

tsx
import { 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)

jsx
function 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)

tsx
function 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)

tsx
function 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)

tsx
import { 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)

typescript
const 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)

typescript
const 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)

tsx
function 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)

tsx
const 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 forma

Misol 15 — Reusable maydon komponenti + useController (2.15, 2.16)

tsx
import { 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)

tsx
const 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)

typescript
const 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

text
 15 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

text
 har 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)

text
 key={index}  (ro'yxat o'zgarsa buziladi — 11.4: 2.13)
 key={field.id}  (RHF beradi — barqaror)

5) Async submit

text
 qo'lda loading state + tugma disable unutish (ikki marta yuborish)
 isSubmitting (avtomatik) + disabled={isSubmitting} (2.12)

6) Server xatosi

text
 server 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: defaultValues berilmagan (qiymat undefineddan boshlandi — 2.11, 11.4). Yechimi: useForm({ defaultValues: {...} }) (har maydonga boshlang'ich qiymat).

Xato 2 — errors.x.message undefined (xato ko'rinmaydi)

Sababi: validatsiya qoidasiga message berilmagan, 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 register ishlatildi 2.9-bob. Yechimi: <Controller> bilan o'ra (Misol 6).

Xato 4 — handleSubmit ishlamaydi (onSubmit chaqirilmaydi)

Sababi: validatsiya o'tmadi (xato bor — handleSubmit faqat valid bo'lsa chaqiradi), yoki tugma type="button". Yechimi: errorsni tekshir (qaysi maydon xato); tugma type="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) yoki register("age", { valueAsNumber: true }) (2.5, 2.6).

Xato 7 — Tahrirlash formasi ma'lumot bilan to'lmaydi

Sababi: ma'lumot async keldi, defaultValues allaqachon bo'sh o'rnatilgan 2.11-bob. Yechimi: useEffectda reset(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 formasi useParams bilan 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).
  • Controller UI kutubxonaga (oddiy input — register — 2.9).
  • mode: "onBlur" (yumshoq xato UX — 2.8).
  • isSubmitting bilan 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).
  • useController reusable maydonga (Controller bir 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, bitta useForm — 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)

  1. RHF setup: useForm + zodResolver, TypeScript bilan (z.infer — Misol 5).
  2. Zod sxema: har bosqich uchun sxema; parol mosligi (refine — Misol 11), coerce (Misol 12).
  3. Bosqichlar: 3 qadam, har biri o'z maydonlari; "Keyingi" faqat joriy bosqich valid bo'lsa (trigger/isValid).
  4. Xato ko'rsatish: field-level, mode: "onBlur", a11y bilan (Misol 13, 2.8, 2.14).
  5. watch: shartli maydon (masalan "sotuvchi" tanlansa do'kon nomi — Misol 7).
  6. useFieldArray: dinamik maydonlar (masalan bir nechta telefon yoki manzil — Misol 10).
  7. Controller: kamida bitta UI kutubxona maydoni (select/date — Misol 6).
  8. Async submit: server'ga yuborish, isSubmitting, server xatolari setError (Misol 9).
  9. Tahrirlash rejimi (bonus): mavjud ma'lumotni reset bilan to'ldirish (Misol 8).
  10. a11y: to'liq label/aria, klaviatura bilan ishlash (Misol 13, 2.14).

Maslahatlar (hint)

  • Bosqichli formada bitta useForm, bosqichni useStateda sakla; "Keyingi"da trigger(["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) setError bilan 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 refine bilan tekshiriladi.
  • Shartli maydon watch bilan ishlaydi.
  • Dinamik maydonlar useFieldArray (key={field.id}).
  • UI kutubxona maydoni Controller bilan.
  • 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'da memo/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/resolverszodResolver (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 DocsFormData, <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!
11.10-bob: Formalar — React Hook Form + Zod validation — Wisar