React Hook Form — murakkab formalar, massiv maydonlar
Muammodan boshlaylik
Rezyume formasi:
- Ish tajribasi — foydalanuvchi "+ Tajriba qo'shish" tugmasi bilan istalgancha blok qo'shadi: kompaniya, lavozim, yillar. Har birini o'chirish yoki tartibini o'zgartirish mumkin
- Shahar — tayyor UI kutubxonasidagi chiroyli
Selectkomponenti. Urefni qabul qilmaydi,onChangedaeventemas, qiymat qaytaradi - Bir necha qadam — 1-qadam: shaxsiy ma'lumotlar, 2-qadam: tajriba, 3-qadam: ko'rib chiqish
register bularning birortasiga ham to'g'ridan-to'g'ri mos emas. React Hook Form da ular uchun maxsus vositalar bor: useFieldArray, Controller va FormProvider.
useFieldArray — dinamik ro'yxat
import { useForm, useFieldArray } from 'react-hook-form';
function Rezyume() {
const { register, control, handleSubmit } = useForm({
defaultValues: {
ism: '',
tajriba: [{ kompaniya: '', lavozim: '', yillar: '' }],
},
});
const { fields, append, remove, move } = useFieldArray({
control,
name: 'tajriba',
});
return (
<form onSubmit={handleSubmit(console.log)}>
<input {...register('ism')} placeholder="Ism" />
<fieldset>
<legend>Ish tajribasi</legend>
{fields.map((field, index) => (
<div key={field.id} className="tajriba-blok">
<input {...register(`tajriba.${index}.kompaniya`)} placeholder="Kompaniya" />
<input {...register(`tajriba.${index}.lavozim`)} placeholder="Lavozim" />
<input
type="number"
{...register(`tajriba.${index}.yillar`, { valueAsNumber: true })}
placeholder="Yillar"
/>
<button type="button" onClick={() => move(index, index - 1)} disabled={index === 0}>
↑
</button>
<button type="button" onClick={() => remove(index)} aria-label={`${index + 1}-tajribani o'chirish`}>
✕
</button>
</div>
))}
<button
type="button"
onClick={() => append({ kompaniya: '', lavozim: '', yillar: '' })}
>
+ Tajriba qo'shish
</button>
</fieldset>
<button type="submit">Saqlash</button>
</form>
);
}
Natija:
{
ism: 'Ali',
tajriba: [
{ kompaniya: 'Uzum', lavozim: 'Frontend', yillar: 2 },
{ kompaniya: 'Payme', lavozim: 'Junior', yillar: 1 },
],
}
Asosiy qoidalar
| Qoida | Nima uchun |
|---|---|
key={field.id} — index emas |
RHF har bir elementga barqaror id beradi; index bilan o'chirish va ko'chirishda qiymatlar aralashib ketadi (27-dars) |
Nom: `tajriba.${index}.kompaniya` |
Massiv ichidagi maydon manzili |
append ga to'liq obyekt bering |
Barcha maydonlar bilan — append({}) emas |
Tugmalarga type="button" |
Aks holda formani yuboradi (62-dars) |
Metodlar
| Metod | Vazifasi |
|---|---|
append(obj) |
Oxiriga qo'shish |
prepend(obj) |
Boshiga |
insert(i, obj) |
Berilgan joyga |
remove(i) |
O'chirish (remove() — hammasini) |
move(from, to) / swap(a, b) |
Tartibni o'zgartirish |
update(i, obj) |
Elementni almashtirish |
replace(arr) |
Butun ro'yxatni almashtirish |
Zod bilan
const rezyumeSchema = z.object({
ism: z.string().min(2),
tajriba: z
.array(
z.object({
kompaniya: z.string().min(1, 'Kompaniyani kiriting'),
lavozim: z.string().min(1, 'Lavozimni kiriting'),
yillar: z.coerce.number().min(0).max(50),
})
)
.min(1, 'Kamida bitta tajriba qo\'shing'),
});
{errors.tajriba?.[index]?.kompaniya && (
<p role="alert">{errors.tajriba[index].kompaniya.message}</p>
)}
{errors.tajriba?.root && <p role="alert">{errors.tajriba.root.message}</p>}
Massivning o'ziga tegishli xato (min(1)) — errors.tajriba.root da.
Controller — tashqi komponentlar
register oddiy HTML input lar uchun ishlaydi: ref bor, onChange(event) bor. UI kutubxonalari (MUI, Ant Design, React Select, sana tanlagichlar) esa ko'pincha boshqacha:
<Select value={...} onChange={(tanlangan) => ...} /> // event emas, qiymat
<DatePicker selected={...} onChange={(sana) => ...} />
Ular uchun — Controller:
import { Controller } from 'react-hook-form';
import Select from 'react-select';
const shaharlar = [
{ value: 'toshkent', label: 'Toshkent' },
{ value: 'samarqand', label: 'Samarqand' },
{ value: 'buxoro', label: 'Buxoro' },
];
<Controller
name="shahar"
control={control}
rules={{ required: 'Shaharni tanlang' }}
render={({ field, fieldState }) => (
<>
<Select
inputId="shahar"
options={shaharlar}
value={shaharlar.find((s) => s.value === field.value) ?? null}
onChange={(tanlangan) => field.onChange(tanlangan?.value)}
onBlur={field.onBlur}
ref={field.ref}
/>
{fieldState.error && <p role="alert">{fieldState.error.message}</p>}
</>
)}
/>
field—value,onChange,onBlur,name,ref; ularni komponentning o'z prop lariga moslangfieldState—error,isTouched,isDirtyfield.onChange(qiymat)ga toza qiymat bering — forma ma'lumotida{ value, label }obyekti emas,'toshkent'saqlansin
Controller — controlled rejim: bu maydon o'zgarganda render bo'ladi. Shuning uchun uni faqat kerakli joyda ishlating; oddiy input lar uchun register.
O'z komponentingiz — useController
import { useController } from 'react-hook-form';
function YulduzBaho({ name, control }) {
const { field } = useController({ name, control, rules: { min: 1 } });
return (
<div role="radiogroup" aria-label="Baho">
{[1, 2, 3, 4, 5].map((n) => (
<button
key={n}
type="button"
role="radio"
aria-checked={field.value === n}
onClick={() => field.onChange(n)}
>
{n <= field.value ? '★' : '☆'}
</button>
))}
</div>
);
}
Qayta ishlatiladigan maydon komponentlari uchun Controller ning hook ko'rinishi.
FormProvider — chuqur komponentlar
Katta formani kichik komponentlarga bo'lsangiz, register, control, errors ni har biriga props bilan uzatish kerak bo'ladi — prop drilling. Yechim — Context:
import { useForm, FormProvider, useFormContext } from 'react-hook-form';
function Rezyume() {
const methods = useForm({ resolver: zodResolver(rezyumeSchema) });
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(saqla)}>
<ShaxsiyMalumotlar />
<TajribaBolimi />
<button type="submit">Saqlash</button>
</form>
</FormProvider>
);
}
function ShaxsiyMalumotlar() {
const { register, formState: { errors } } = useFormContext();
return (
<fieldset>
<legend>Shaxsiy ma'lumotlar</legend>
<input {...register('ism')} />
{errors.ism && <p role="alert">{errors.ism.message}</p>}
</fieldset>
);
}
useFormContext() — useForm qaytargan hamma narsani beradi.
Ko'p qadamli forma
const qadamMaydonlari = [
['ism', 'email', 'telefon'], // 1-qadam
['tajriba'], // 2-qadam
[], // 3-qadam — ko'rib chiqish
];
function KopQadamliForma() {
const [qadam, setQadam] = useState(0);
const methods = useForm({
resolver: zodResolver(rezyumeSchema),
mode: 'onTouched',
shouldUnregister: false, // yashirilgan qadamlar qiymatlari saqlansin (standart)
});
async function keyingi() {
const togri = await methods.trigger(qadamMaydonlari[qadam]); // faqat shu qadamni tekshirish
if (togri) setQadam((q) => q + 1);
}
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(yuborish)}>
<p aria-live="polite">{qadam + 1} / 3-qadam</p>
{qadam === 0 && <ShaxsiyMalumotlar />}
{qadam === 1 && <TajribaBolimi />}
{qadam === 2 && <KoribChiqish />}
{qadam > 0 && (
<button type="button" onClick={() => setQadam((q) => q - 1)}>Orqaga</button>
)}
{qadam < 2 ? (
<button type="button" onClick={keyingi}>Keyingi</button>
) : (
<button type="submit">Yuborish</button>
)}
</form>
</FormProvider>
);
}
trigger(['ism', 'email'])— faqat ko'rsatilgan maydonlarni tekshiradi;Promise<boolean>qaytaradi- Qadamlar orasida qiymatlar saqlanadi — bitta
useForm, faqat ko'rinadigan qism almashadi - Ko'rib chiqish qadamida —
getValues()bilan barcha ma'lumotni ko'rsatish
[!TIP] Uzun formalarda foydalanuvchi ma'lumotini yo'qotmasligi uchun qoralamani saqlang:
watchbilan o'zgarishlarni kuzatib, debounce bilanlocalStoragega yozing vauseForm({ defaultValues: saqlangan })bilan qaytaring. Sahifa tasodifan yopilsa ham — ish yo'qolmaydi.
Nima uchun shunday?
Nima uchun useFieldArray da key uchun index emas, field.id kerak — elementlar tartibi baribir index bilan aniqlanadi-ku?
Chunki key — React uchun elementning shaxsi, uning tartibi emas. Ikkinchi tajriba blokini o'chirsangiz, uchinchisi ikkinchi o'ringa siljiydi. key={index} bo'lsa, React "1-indexli element hali ham bor, faqat oxirgisi yo'qoldi" deb o'ylaydi va DOM dagi input larni joyida qoldiradi — natijada o'chirilgan blokning qiymatlari ekranda qoladi, oxirgi blok esa yo'qoladi. RHF qiymatlarni DOM dan o'qigani uchun (63-dars) bu xato ayniqsa og'ir: ekranda bir narsa, forma ma'lumotida boshqa narsa. field.id — har bir element yaratilganda beriladigan va u bilan birga ko'chadigan barqaror identifikator; React uni kuzatadi va DOM ni to'g'ri yangilaydi.
Xulosa
Eslab qoling:
useFieldArray({ control, name })→fields,append,remove,move,insert,updatekey={field.id}— index emas; nomlar:`tajriba.${index}.kompaniya`appendga to'liq obyekt; tugmalargatype="button"- Massiv xatolari:
errors.tajriba?.[i]?.maydon,errors.tajriba?.root Controller/useController—refsiz yoki qiymat qaytaradigan tashqi komponentlar uchunFormProvider+useFormContext— katta formani komponentlarga bo'lish- Ko'p qadamli forma — bitta
useForm,trigger(maydonlar)bilan qadamni tekshirish
Keyingi bo'lim
10-bo'lim: Performance — React qanday render qiladi va ilovani qanday tezlashtirish mumkin.