React Hook Form — validation, error xabarlar

~20 daq · React · Formalar

Muammodan boshlaylik

Brauzerning o'z validatsiyasi (HTML kursi, 19-dars) yaxshi, lekin:

  • Xato xabarlari brauzer tilida va uslubida — dizayn bilan mos emas, ko'pincha inglizcha
  • Xabar bir necha soniyada yo'qoladi
  • "Parollar mos kelsin", "bu login band emasmi?" kabi qoidalarni yozib bo'lmaydi
  • Barcha xatolarni birdaniga ko'rsatib bo'lmaydi

React Hook Form validatsiya qoidalarini register ning ikkinchi argumentida qabul qiladi va xatolarni formState.errors da beradi — ularni o'zingiz xohlagan ko'rinishda chiqarasiz.


Qoidalar

<input
  {...register('ism', {
    required: 'Ismni kiriting',
    minLength: { value: 2, message: 'Kamida 2 ta harf' },
    maxLength: { value: 50, message: "Ko'pi bilan 50 ta harf" },
  })}
/>
Qoida Qiymat Misol
required true yoki xabar required: 'Majburiy maydon'
minLength / maxLength { value, message } minLength: { value: 8, message: '...' }
min / max { value, message } min: { value: 18, message: '18 yoshdan katta' }
pattern { value: RegExp, message } pattern: { value: /^\+998\d{9}$/, message: '...' }
validate Funksiya yoki funksiyalar obyekti Pastda

Qisqa yozuv: minLength: 8 — xabarsiz (xato bo'ladi, lekin message bo'sh).


Xatolarni ko'rsatish

function Royxat() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  return (
    <form onSubmit={handleSubmit(console.log)} noValidate>
      <label htmlFor="email">Email</label>
      <input
        id="email"
        type="email"
        aria-invalid={errors.email ? 'true' : 'false'}
        aria-describedby={errors.email ? 'email-xato' : undefined}
        {...register('email', {
          required: 'Emailni kiriting',
          pattern: {
            value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
            message: "Email formati noto'g'ri",
          },
        })}
      />
      {errors.email && (
        <p id="email-xato" role="alert" className="xato">
          {errors.email.message}
        </p>
      )}

      <button type="submit">Yuborish</button>
    </form>
  );
}
  • errors.email — xato bo'lsa obyekt: { type: 'required', message: 'Emailni kiriting' }
  • noValidate — brauzerning o'z xabarlarini o'chiradi; endi faqat sizning xabarlaringiz
  • aria-invalid va aria-describedby — ekran o'quvchi "Email, noto'g'ri, Emailni kiriting" deb o'qiydi (HTML kursi, 21-dars)
  • role="alert" — xato paydo bo'lganda darhol o'qiladi

Qayta ishlatiladigan maydon komponenti

Har bir maydon uchun bir xil kodni takrorlamaslik uchun:

import { cloneElement } from 'react';

function Maydon({ label, name, xato, children }) {
  const xatoId = `${name}-xato`;
  return (
    <div className="maydon">
      <label htmlFor={name}>{label}</label>
      {cloneElement(children, {
        id: name,
        'aria-invalid': xato ? 'true' : 'false',
        'aria-describedby': xato ? xatoId : undefined,
      })}
      {xato && <p id={xatoId} role="alert">{xato.message}</p>}
    </div>
  );
}
<Maydon label="Ism" name="ism" xato={errors.ism}>
  <input {...register('ism', { required: 'Ismni kiriting' })} />
</Maydon>

validate — o'z qoidangiz

<input
  {...register('login', {
    required: 'Loginni kiriting',
    validate: (qiymat) =>
      !qiymat.includes(' ') || "Login da bo'sh joy bo'lmasin",
  })}
/>

validate funksiyasi:

  • true qaytarsa — to'g'ri
  • string qaytarsa — shu xabar bilan xato
  • false qaytarsa — xabarsiz xato

Bir nechta qoida

{...register('parol', {
  required: 'Parolni kiriting',
  validate: {
    uzunlik: (v) => v.length >= 8 || 'Kamida 8 belgi',
    raqam: (v) => /\d/.test(v) || 'Kamida bitta raqam',
    katta: (v) => /[A-Z]/.test(v) || 'Kamida bitta katta harf',
  },
})}

errors.parol.type — qaysi qoida buzilgani: 'raqam', 'katta'...

Boshqa maydonga bog'liq qoida

const { register, getValues } = useForm();

<input type="password" {...register('parol', { required: true })} />

<input
  type="password"
  {...register('parolTakror', {
    required: 'Parolni takrorlang',
    validate: (v) => v === getValues('parol') || 'Parollar mos kelmadi',
  })}
/>

Async — server tekshiruvi

{...register('login', {
  validate: async (login) => {
    const j = await fetch(`/api/login-bandmi?login=${encodeURIComponent(login)}`);
    const { band } = await j.json();
    return !band || 'Bu login band';
  },
})}

Async validate har tekshiruvda so'rov yuboradi — mode bilan birga ehtiyot bo'lib ishlating (pastda).


mode — qachon tekshirish

const { register } = useForm({
  mode: 'onTouched',
  reValidateMode: 'onChange',
});
mode Qachon tekshiradi Foydalanuvchi tajribasi
onSubmit (standart) Faqat yuborishda Kam bezovta, lekin xato kech ko'rinadi
onBlur Maydondan chiqqanda Yaxshi muvozanat
onTouched Birinchi blur dan keyin, keyin har o'zgarishda Ko'p hollarda eng yaxshi
onChange Har harfda Bezovta qiladi — yozib bo'lmay turib "noto'g'ri!"
all Blur va change —

reValidateMode — yuborishga urinishdan keyin qachon qayta tekshirish (standart — onChange: foydalanuvchi xatoni tuzatayotganda xabar darhol yo'qoladi).

[!TIP] Foydalanuvchi yozib bo'lmasdan turib xato ko'rsatmang. onTouched — maydondan birinchi marta chiqqandan keyin tekshiradi, keyin esa tuzatish jarayonida xato darhol yo'qoladi. Bu HTML dagi :user-invalid (HTML kursi, 19-dars) ning xatti-harakatiga o'xshaydi.


Server xatolari — setError

Server javob berdi: "Bu email bilan foydalanuvchi allaqachon mavjud". Xatoni tegishli maydon yonida ko'rsatish:

const { register, handleSubmit, setError, formState: { errors } } = useForm();

async function onSubmit(data) {
  const j = await fetch('/api/royxat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });

  if (j.status === 409) {
    setError('email', { type: 'server', message: "Bu email allaqachon ro'yxatdan o'tgan" });
    return;
  }

  if (!j.ok) {
    setError('root.server', { message: "Server xatosi. Keyinroq urinib ko'ring." });
  }
}
{errors.root?.server && <p role="alert">{errors.root.server.message}</p>}
  • setError('maydon', ...) — aniq maydonga xato
  • setError('root.nimadir', ...) — butun formaga tegishli xato
  • clearErrors() — xatolarni tozalash

Muammo: qoidalar JSX ichida

Katta formada qoidalar JSX bo'ylab tarqalib ketadi: ularni qayta ishlatib, testlab, server bilan bo'lishib bo'lmaydi. TypeScript da forma turi va qoidalar alohida yoziladi va bir-biridan farq qilib qolishi mumkin.

Yechim — barcha qoidalarni bitta sxema da yozish. Keyingi dars — Zod.


Nima uchun shunday?

Nima uchun xatolarni foydalanuvchi maydondan chiqqandan keyin ko'rsatish (onTouched) — darhol (onChange) dan yaxshiroq?

Chunki foydalanuvchi hali yozib bo'lmagan. Email maydoniga a harfini yozishi bilan "Email formati noto'g'ri" deyish — xuddi gapirayotgan odamning gapini birinchi so'zidayoq "noto'g'ri!" deb bo'lishga o'xshaydi. Tadqiqotlar shuni ko'rsatadiki, bunday "erta" xatolar foydalanuvchini asabiylashtiradi va formani tugatish ehtimolini kamaytiradi. Boshqa tomondan, faqat yuborishda tekshirish ham yomon: foydalanuvchi 12 ta maydonni to'ldirib, keyin 5 ta xatoni birdaniga ko'radi. onTouched — o'rta yo'l: maydon tugatilgandan keyin baholanadi, va xato tuzatilayotganda darhol yo'qoladi — ijobiy fikr-mulohaza tez, salbiy esa o'z vaqtida.


Xulosa

Eslab qoling:

  • Qoidalar — register('nom', { required, minLength, maxLength, min, max, pattern, validate })
  • Xabar bilan: { value, message } yoki required: 'Xabar'
  • errors.nom?.message — xato matni; noValidate — brauzer xabarlari o'chiriladi
  • Accessibility: aria-invalid, aria-describedby, role="alert"
  • validate — true yoki xabar string; bir nechta qoida — obyekt; async ham mumkin
  • Bog'liq maydonlar — getValues('parol')
  • mode: 'onTouched' — ko'p hollarda eng yaxshi
  • Server xatolari — setError('maydon') yoki setError('root.server')

Keyingi dars

65-dars: React Hook Form — Zod bilan schema validation