Validatsiya — Zod, xato xabarlari

~15 daq · React Native · Ro'yxatlar va formalar

Muammodan boshlaylik

Ro'yxatdan o'tish formasi. rules bilan (21-dars):

rules={{ required: '...', minLength: {...}, pattern: {...}, validate: (v) => ... }}

Muammolar:

  • Qoidalar JSX ichida sochilib ketgan — formaning "shartnomasi" ni bir joyda ko'rib bo'lmaydi
  • "Parol va tasdiqlash mos kelsin" kabi bir nechta maydonni bog'laydigan qoidalar noqulay
  • Bir xil qoidalar backend da qayta yoziladi
  • TypeScript tipi alohida, qoidalar alohida — ular bir-biridan uzoqlashib ketadi

Zod — ma'lumot sxemasini bir marta yozish: undan validatsiya ham, TypeScript tipi ham olinadi.


O'rnatish

npm install zod @hookform/resolvers

Sxema

import { z } from 'zod';

export const royxatSxema = z
  .object({
    ism: z.string().trim().min(2, 'Ism kamida 2 harf').max(50, 'Ism 50 harfdan oshmasin'),
    telefon: z.string().regex(/^\+998\d{9}$/, 'Format: +998901234567'),
    email: z.string().trim().toLowerCase().email("Email noto'g'ri"),
    yosh: z.coerce.number({ message: 'Raqam kiriting' }).int().min(14, 'Kamida 14 yosh').max(120),
    parol: z
      .string()
      .min(8, 'Kamida 8 belgi')
      .regex(/[A-Z]/, 'Kamida bitta katta harf')
      .regex(/\d/, 'Kamida bitta raqam'),
    parolTakror: z.string(),
    shartlar: z.literal(true, { message: 'Shartlarni qabul qiling' }),
  })
  .refine((d) => d.parol === d.parolTakror, {
    message: 'Parollar mos emas',
    path: ['parolTakror'],
  });

export type RoyxatForma = z.infer<typeof royxatSxema>;
  • Har bir maydon — tur + qoidalar + o'zbekcha xabar
  • .trim(), .toLowerCase() — validatsiyadan oldin tozalash
  • z.coerce.number() — TextInput dan kelgan "25" matnini songa aylantiradi
  • .refine — bir nechta maydonni bog'laydigan qoida; path — xato qaysi maydon tagida chiqishi
  • z.infer — sxemadan TypeScript tipi. Tipni qo'lda yozish shart emas, va ular hech qachon farqlanmaydi

React Hook Form ga ulash

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { royxatSxema, RoyxatForma } from '@/schemas/royxat';
import { FormInput } from '@/components/FormInput';

export default function Royxat() {
  const { control, handleSubmit, formState: { isSubmitting } } = useForm<RoyxatForma>({
    resolver: zodResolver(royxatSxema),
    mode: 'onTouched',
    defaultValues: { ism: '', telefon: '+998', email: '', parol: '', parolTakror: '' },
  });

  async function yuborish(data: RoyxatForma) {
    // data — allaqachon tozalangan va tiplangan: email kichik harfda, yosh — son
    await api.royxatdanOtish(data);
  }

  return (
    <ScrollView contentContainerStyle={{ padding: 16, gap: 14 }} keyboardShouldPersistTaps="handled">
      <FormInput control={control} name="ism" label="Ism" autoComplete="name" />
      <FormInput control={control} name="telefon" label="Telefon" keyboardType="phone-pad" />
      <FormInput control={control} name="email" label="Email" keyboardType="email-address" autoCapitalize="none" />
      <FormInput control={control} name="yosh" label="Yosh" keyboardType="number-pad" />
      <FormInput control={control} name="parol" label="Parol" secureTextEntry />
      <FormInput control={control} name="parolTakror" label="Parolni takrorlang" secureTextEntry />
      {/* shartlar — Switch bilan Controller */}
      <Tugma matn="Ro'yxatdan o'tish" onPress={handleSubmit(yuborish)} yuklanmoqda={isSubmitting} />
    </ScrollView>
  );
}

rules endi kerak emas — hamma qoidalar sxemada. FormInput (21-dars) o'zgarishsiz ishlaydi.

Switch va Controller

<Controller
  control={control}
  name="shartlar"
  render={({ field: { value, onChange }, fieldState: { error } }) => (
    <View>
      <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
        <Switch value={!!value} onValueChange={onChange} />
        <Text>Foydalanish shartlariga roziman</Text>
      </View>
      {error ? <Text style={{ color: '#dc2626' }}>{error.message}</Text> : null}
    </View>
  )}
/>

Sxemani qayta ishlatish

// schemas/umumiy.ts
export const telefonSxema = z.string().regex(/^\+998\d{9}$/, 'Format: +998901234567');
export const emailSxema = z.string().trim().toLowerCase().email("Email noto'g'ri");

// schemas/profil.ts
export const profilSxema = z.object({
  ism: z.string().trim().min(2),
  telefon: telefonSxema,
  email: emailSxema.optional(),
});

// Tahrirlashda — barcha maydonlar ixtiyoriy
export const profilYangilashSxema = profilSxema.partial();

API javobini tekshirish

Zod nafaqat formalar uchun. Serverdan kelgan ma'lumot ham kutilgan shaklda ekanini tekshirish mumkin:

const mahsulotSxema = z.object({
  id: z.number(),
  nom: z.string(),
  narx: z.number().nonnegative(),
  rasm: z.string().url().nullable(),
});

const javob = await fetch(url).then((r) => r.json());
const mahsulot = mahsulotSxema.parse(javob);   // shakl noto'g'ri bo'lsa — aniq xato

Backend jamoasi API ni o'zgartirib yuborsa — ilova tushunarsiz undefined is not an object bilan emas, aniq "narx maydoni kutilgan edi" xatosi bilan to'xtaydi.

Agar backend Node.js / NestJS da bo'lsa — bitta sxema ham server, ham mobil ilova uchun ishlatiladi (monorepo yoki umumiy paket).


Yaxshi xato xabarlari

  • Nima noto'g'ri va qanday tuzatish: "Noto'g'ri qiymat" emas, "Format: +998901234567"
  • Foydalanuvchi tilida: "regex mismatch" emas
  • Maydon yonida — tepadagi umumiy ro'yxatda emas
  • Vaqtida: maydondan chiqqanda (onTouched), har harfda emas
  • Server xatolari ham maydonga bog'lansin (setError, 21-dars)

Nima uchun shunday?

Nima uchun frontend da validatsiya qilamiz, agar server baribir tekshiradi?

Ular turli vazifani bajaradi — xuddi PHP kursidagi kabi (20-dars). Ilovadagi validatsiya — foydalanuvchi tajribasi uchun: darhol, tarmoqsiz, aniq maydon yonida xabar. Serverdagi validatsiya — xavfsizlik va ma'lumot yaxlitligi uchun: ilovani chetlab, API ga to'g'ridan-to'g'ri so'rov yuborish mumkin. Zod bu ikkalasini bitta sxema bilan qilish imkonini beradi — qoidalar ikki joyda turlicha yozilib, bir-biridan farqlanib qolmaydi.


Xulosa

Eslab qoling:

  • Zod — sxema bir marta: validatsiya + TypeScript tipi (z.infer)
  • .trim(), .toLowerCase(), z.coerce.number() — tozalash va aylantirish
  • .refine(..., { path }) — bog'liq maydonlar (parol tasdiqlash)
  • zodResolver(sxema) — React Hook Form bilan; rules kerak emas
  • Sxemalarni qayta ishlatish: umumiy qismlar, .partial()
  • sxema.parse(javob) — API javobini tekshirish
  • Xabarlar: aniq, o'zbekcha, maydon yonida, vaqtida

Keyingi dars

23-dars: Klaviatura — KeyboardAvoidingView, fokus, yopish