Formalar — React Hook Form bilan

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

Muammodan boshlaylik

Yetkazib berish manzili formasi: ism, telefon, shahar, ko'cha, uy, xonadon, izoh — 7 ta maydon. useState bilan:

const [ism, setIsm] = useState('');
const [telefon, setTelefon] = useState('');
const [shahar, setShahar] = useState('');
// ... yana 4 ta
const [xatolar, setXatolar] = useState<Record<string, string>>({});
const [teginildi, setTeginildi] = useState<Record<string, boolean>>({});

Har bir maydon uchun holat, xato, "teginilganmi", validatsiya, yuborish... Va har bir harf kiritilganda butun forma qayta chiziladi. 7 maydonli formada kod 150 qatorga yetadi.

React Hook Form — formalar uchun eng ommabop kutubxona: kam kod, kam qayta chizish, validatsiya va xatolar tayyor.


O'rnatish

npm install react-hook-form

Asosiy naqsh: Controller

Vebda RHF register bilan ishlaydi. React Native da TextInput boshqacha — shuning uchun Controller:

import { useForm, Controller } from 'react-hook-form';
import { View, Text, TextInput, Pressable, StyleSheet } from 'react-native';

type ManzilForma = {
  ism: string;
  telefon: string;
  kocha: string;
  izoh: string;
};

export default function Manzil() {
  const {
    control,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<ManzilForma>({
    defaultValues: { ism: '', telefon: '+998', kocha: '', izoh: '' },
  });

  async function yuborish(data: ManzilForma) {
    await new Promise((r) => setTimeout(r, 1000));   // API so'rovi o'rniga
    console.log('Yuborildi:', data);
  }

  return (
    <View style={styles.ekran}>
      <Controller
        control={control}
        name="ism"
        rules={{ required: 'Ismingizni kiriting', minLength: { value: 2, message: 'Kamida 2 harf' } }}
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            style={[styles.input, errors.ism && styles.xatoInput]}
            placeholder="Ism"
            value={value}
            onChangeText={onChange}
            onBlur={onBlur}
            autoComplete="name"
          />
        )}
      />
      {errors.ism && <Text style={styles.xato}>{errors.ism.message}</Text>}

      <Controller
        control={control}
        name="telefon"
        rules={{
          required: 'Telefon kerak',
          pattern: { value: /^\+998\d{9}$/, message: 'Format: +998901234567' },
        }}
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            style={[styles.input, errors.telefon && styles.xatoInput]}
            placeholder="+998901234567"
            keyboardType="phone-pad"
            value={value}
            onChangeText={onChange}
            onBlur={onBlur}
          />
        )}
      />
      {errors.telefon && <Text style={styles.xato}>{errors.telefon.message}</Text>}

      {/* kocha, izoh — xuddi shunday */}

      <Pressable
        style={[styles.tugma, isSubmitting && { opacity: 0.6 }]}
        onPress={handleSubmit(yuborish)}
        disabled={isSubmitting}
      >
        <Text style={styles.tugmaMatn}>{isSubmitting ? 'Yuborilmoqda...' : 'Saqlash'}</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  ekran: { flex: 1, padding: 16, gap: 8, backgroundColor: 'white' },
  input: { borderWidth: 1, borderColor: '#cbd5e1', borderRadius: 10, padding: 14, fontSize: 16 },
  xatoInput: { borderColor: '#dc2626' },
  xato: { color: '#dc2626', fontSize: 13, marginTop: -4 },
  tugma: { backgroundColor: '#0284c7', padding: 16, borderRadius: 12, alignItems: 'center', marginTop: 8 },
  tugmaMatn: { color: 'white', fontSize: 16, fontWeight: '600' },
});
Element Vazifasi
useForm<T>({ defaultValues }) Forma, tiplangan
Controller Maydonni formaga ulash
rules Validatsiya qoidalari
field.onChange onChangeText ga ulanadi
handleSubmit(fn) Avval validatsiya; hammasi to'g'ri bo'lsa — fn(data)
errors.ism?.message Xato matni
isSubmitting fn bajarilayotganda true (async ham)

defaultValues — majburiy odat: busiz maydonlar undefined bilan boshlanadi va "uncontrolled → controlled" ogohlantirishi chiqadi.


Takrorlanishni olib tashlash: FormInput

Har bir maydon uchun Controller + TextInput + xato — takrorlanish. Bitta komponentga:

// components/FormInput.tsx
import { Control, Controller, FieldValues, Path, RegisterOptions } from 'react-hook-form';
import { TextInput, TextInputProps, Text, View, StyleSheet } from 'react-native';

type Props<T extends FieldValues> = TextInputProps & {
  control: Control<T>;
  name: Path<T>;
  label: string;
  rules?: RegisterOptions<T, Path<T>>;
};

export function FormInput<T extends FieldValues>({ control, name, label, rules, ...props }: Props<T>) {
  return (
    <Controller
      control={control}
      name={name}
      rules={rules}
      render={({ field: { value, onChange, onBlur }, fieldState: { error } }) => (
        <View style={{ gap: 6 }}>
          <Text style={styles.label}>{label}</Text>
          <TextInput
            style={[styles.input, error && styles.xatoInput]}
            value={value}
            onChangeText={onChange}
            onBlur={onBlur}
            placeholderTextColor="#94a3b8"
            {...props}
          />
          {error ? <Text style={styles.xato}>{error.message}</Text> : null}
        </View>
      )}
    />
  );
}

const styles = StyleSheet.create({
  label: { fontSize: 14, fontWeight: '600', color: '#334155' },
  input: { borderWidth: 1, borderColor: '#cbd5e1', borderRadius: 10, padding: 14, fontSize: 16 },
  xatoInput: { borderColor: '#dc2626' },
  xato: { color: '#dc2626', fontSize: 13 },
});

Endi forma:

<FormInput control={control} name="ism" label="Ism" rules={{ required: 'Ismingizni kiriting' }} autoComplete="name" />
<FormInput control={control} name="telefon" label="Telefon" keyboardType="phone-pad"
  rules={{ required: 'Telefon kerak', pattern: { value: /^\+998\d{9}$/, message: 'Format: +998901234567' } }} />
<FormInput control={control} name="izoh" label="Izoh" multiline numberOfLines={3} />

name — TypeScript bilan tekshiriladi: name="ismm" — kompilyatsiya xatosi.

fieldState.error — har bir maydon o'z xatosini o'zi biladi.


Validatsiya qachon ishlaydi

useForm({ mode: 'onTouched' });
mode Qachon
onSubmit (standart) Faqat yuborishda; keyin har o'zgarishda
onTouched Maydondan chiqqanda (blur) — mobil uchun eng yaxshi
onChange Har bir harfda — bezovta qiladi

onTouched — foydalanuvchi yozishni tugatmaguncha xato ko'rsatmaydi, lekin keyingi maydonga o'tganda darhol xabar beradi.


Boshqa imkoniyatlar

const { reset, setValue, watch, setError, setFocus } = useForm<ManzilForma>();

reset();                                   // formani tozalash (yuborgandan keyin)
reset(serverdanKelganManzil);              // tahrirlash formasi uchun qiymatlarni yuklash
setValue('kocha', 'Amir Temur 15');         // kodda qiymat berish (xaritadan tanlanganda)
const shahar = watch('shahar');            // qiymatni kuzatish — shartli maydonlar uchun
setError('telefon', { message: 'Bu raqam allaqachon ro\'yxatdan o\'tgan' });   // server xatosi
setFocus('ism');                           // TextInput ref ulangan bo'lsa

Server xatosini maydonga bog'lash:

async function yuborish(data: ManzilForma) {
  const javob = await api.manzilSaqlash(data);
  if (javob.xatolar?.telefon) {
    setError('telefon', { message: javob.xatolar.telefon });
    return;
  }
  reset();
}

Nima uchun shunday?

Nima uchun React Hook Form har bir harfda butun formani qayta chizmaydi?

useState bilan har bir maydon qiymati komponent holatida — o'zgarsa, butun forma komponenti qayta chiziladi. RHF esa qiymatlarni o'zining ichki obyektida (ref) saqlaydi va faqat o'zgargan maydonni (Controller ni) qayta chizadi. Ota komponent esa faqat kerakli paytda — xatolar yoki isSubmitting o'zgarganda — yangilanadi. Mobil qurilmalarda, ayniqsa uzun formalarda va eski telefonlarda, bu klaviatura bilan yozishni sezilarli silliqroq qiladi.


Xulosa

Eslab qoling:

  • useForm<T>({ defaultValues, mode: 'onTouched' })
  • React Native da Controller — field.onChange → onChangeText
  • rules: required, minLength, pattern, validate
  • handleSubmit(fn) — validatsiyadan keyin yuboradi; isSubmitting — tugmani o'chirish
  • Qayta ishlatiladigan FormInput<T> — tiplangan name, o'z xatosi bilan
  • reset, setValue, watch, setError — tahrirlash, shartli maydonlar, server xatolari

Keyingi dars

22-dars: Validatsiya — Zod, xato xabarlari