Формы с повторяющимися группами полей представляют собой структуру, в которой один и тот же набор значений может существовать в нескольких экземплярах. Типичный пример — список адресов пользователя, перечень товаров в заказе, набор контактов или этапов процесса. В таких сценариях данные естественным образом выражаются через массив объектов.
В контексте работы с YupResolver и
react-hook-form такие структуры требуют согласованного
описания схемы валидации и корректного связывания с динамическими полями
формы.
Основой выступает массив объектов. Каждый объект описывает одну группу полей:
const defaultValues = {
contacts: [
{
name: "",
email: "",
phone: ""
}
]
};
Структура фиксирует ключевую особенность: форма не оперирует отдельными независимыми полями, а управляет коллекцией однотипных сущностей.
Библиотека Yup предоставляет специализированный API для описания
массивов через yup.array().of(yup.object()).
import * as yup from "yup";
const contactSchema = yup.object({
name: yup.string().required("Имя обязательно"),
email: yup.string().email("Некорректный email").required("Email обязателен"),
phone: yup.string().min(10, "Минимум 10 символов")
});
const schema = yup.object({
contacts: yup.array().of(contactSchema)
.min(1, "Должен быть хотя бы один контакт")
});
Ключевой момент заключается в том, что валидация применяется на уровне каждого элемента массива, а также на уровне всей коллекции.
YupResolver выступает связующим слоем между схемой Yup и
механизмом валидации react-hook-form.
import { useForm, useFieldArray } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
const form = useForm({
defaultValues,
resolver: yupResolver(schema)
});
При каждом изменении данных форма проходит через резолвер, который сопоставляет структуру значений с описанной схемой и формирует объект ошибок.
Для работы с повторяющимися блоками используется
useFieldArray, который управляет добавлением и удалением
элементов массива.
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "contacts"
});
Каждый элемент fields соответствует одной группе полей и
содержит стабильный идентификатор, необходимый для корректного
рендеринга.
Структура UI строится через итерацию по массиву
fields.
return (
<form onSub mit={form.handleSubmit(onSubmit)}>
{fields.map((field, index) => (
<div key={field.id}>
<input
{...form.register(`contacts.${index}.name`)}
placeholder="Имя"
/>
<input
{...form.register(`contacts.${index}.email`)}
placeholder="Email"
/>
<input
{...form.register(`contacts.${index}.phone`)}
placeholder="Телефон"
/>
<button type="button" onCl ick={() => remove(index)}>
Удалить
</button>
</div>
))}
<button
type="button"
onCl ick={() => append({ name: "", email: "", phone: "" })}
>
Добавить контакт
</button>
<button type="submit">Сохранить</button>
</form>
);
При использовании YupResolver ошибки формируются в
иерархической структуре, отражающей путь к полю:
errors.contacts?.[0]?.email
или при динамическом доступе:
errors.contacts?.[index]?.name
Каждая ошибка привязана к конкретному элементу массива, что позволяет точно отображать состояние валидации в UI.
В Yup доступны дополнительные ограничения для управления структурой коллекции:
const schema = yup.object({
contacts: yup.array()
.of(contactSchema)
.min(1, "Минимум один элемент")
.max(5, "Максимум пять элементов")
.required()
});
Также возможно добавление пользовательских проверок:
contacts: yup.array().test(
"unique-emails",
"Email должен быть уникальным",
(value) => {
const emails = value?.map(v => v.email);
return new Set(emails).size === emails.length;
}
)
Повторяющиеся группы часто содержат вложенные массивы, формируя многоуровневую модель.
const schema = yup.object({
orders: yup.array().of(
yup.object({
title: yup.string().required(),
items: yup.array().of(
yup.object({
name: yup.string().required(),
quantity: yup.number().min(1)
})
)
})
)
});
В таких структурах путь ошибки становится глубже:
orders[0].items[2].quantity
Добавление вложенных структур требует полного соответствия default-объекту:
append({
title: "",
items: [
{
name: "",
quantity: 1
}
]
});
Несоответствие структуры приводит к рассинхронизации между
react-hook-form и Yup-схемой.
При работе с массивами полей важно учитывать стоимость обновлений
состояния. Каждый append или remove инициирует
перерасчёт всей формы. Оптимизация достигается за счёт:
key через
field.idИспользование индекса массива как ключа рендера приводит к некорректному сопоставлению значений при удалении элементов.
// Нежелательный подход
fields.map((field, index) => (
<div key={index}>
Корректный вариант основан на field.id, предоставляемом
useFieldArray.
Неконсистентность структуры defaultValues и схемы Yup
приводит к отсутствию валидации или невозможности корректного
отображения ошибок. Особое внимание требуется при наличии вложенных
массивов и условных полей.
Yup позволяет задавать зависимости между полями внутри одного объекта массива:
const contactSchema = yup.object({
type: yup.string().required(),
value: yup.string().when("type", {
is: "email",
then: schema => schema.email().required(),
otherwise: schema => schema.required()
})
});
Такая конструкция сохраняет целостность логики внутри каждого элемента массива независимо от остальных.