Библиотека Final Form управляет состоянием формы через подписочную модель, где каждое поле регистрируется как отдельная сущность с собственным жизненным циклом значений, ошибок и состояния взаимодействия. Yup используется как декларативный валидатор схем, позволяющий описывать структуру данных формы и правила проверки через единый объект схемы.
Final Form ожидает функцию валидации, которая возвращает объект
ошибок с ключами, совпадающими с именами полей. Yup, в свою очередь,
работает со схемой и возвращает либо валидированные данные, либо
структурированное исключение ValidationError. Связка
формируется через адаптер, преобразующий ошибки Yup в формат Final
Form.
Ключевая особенность интеграции заключается в том, что Yup выполняет всю валидацию централизованно, а Final Form распределяет результат по конкретным полям.
Схема описывает структуру данных и правила проверки:
import * as Yup from 'yup';
const schema = Yup.object({
email: Yup.string()
.email('Некорректный email')
.required('Email обязателен'),
password: Yup.string()
.min(8, 'Минимум 8 символов')
.required('Пароль обязателен')
});
Схема отражает структуру состояния формы и используется как единый источник истины для валидации.
Final Form требует функцию вида (values) => errors.
Yup возвращает исключение, поэтому требуется преобразование:
const validate = async (values) => {
try {
await schema.validate(values, { abortEarly: false });
return {};
} catch (err) {
const errors = {};
if (err.inner) {
err.inner.forEach((error) => {
if (!errors[error.path]) {
errors[error.path] = error.message;
}
});
}
return errors;
}
};
Параметр abortEarly: false необходим для накопления всех
ошибок за одну проверку, иначе возвращается только первая найденная
ошибка.
Final Form принимает функцию валидации при создании формы:
import { createForm } from 'final-form';
const form = createForm({
onSubmit: (values) => {
console.log(values);
},
validate
});
Каждое изменение значения запускает проверку, результат которой синхронизируется с состоянием формы.
Yup поддерживает вложенные объекты и массивы, Final Form требует точного соответствия путей ошибок.
const schema = Yup.object({
user: Yup.object({
name: Yup.string().required('Имя обязательно'),
contacts: Yup.array().of(
Yup.object({
phone: Yup.string().required('Телефон обязателен')
})
)
})
});
Ошибки должны возвращаться в формате:
{
"user.name": "Имя обязательно",
"user.contacts[0].phone": "Телефон обязателен"
}
Для корректного преобразования используется рекурсивная обработка
error.path.
Структурирование ошибок для Final Form требует нормализации путей:
const formatYupErrors = (yupError) => {
const errors = {};
yupError.inner.forEach((err) => {
const path = err.path;
if (!errors[path]) {
errors[path] = err.message;
}
});
return errors;
};
В сложных формах с массивами требуется расширение логики для поддержки индексов.
Final Form поддерживает асинхронную валидацию, что позволяет использовать Yup с серверными проверками:
const validate = async (values) => {
try {
await schema.validate(values, { abortEarly: false });
const response = await fetch('/api/check', {
method: 'POST',
body: JSON.stringify(values)
});
const data = await response.json();
if (!data.ok) {
return {
email: 'Email уже используется'
};
}
return {};
} catch (err) {
return formatYupErrors(err);
}
};
Асинхронная логика выполняется последовательно: сначала схема, затем серверные ограничения.
Yup поддерживает динамические правила через when, что
особенно полезно в формах с зависимыми полями:
const schema = Yup.object({
password: Yup.string().required(),
confirmPassword: Yup.string().when('password', (password, field) =>
password ? field.required('Подтверждение обязательно') : field
)
});
Final Form передает актуальные значения, что позволяет Yup пересчитывать условия на лету.
При больших формах полная валидация может быть затратной. Используется стратегия сегментирования схемы:
const fieldSchemas = {
email: Yup.string().email().required(),
password: Yup.string().min(8)
};
const validate = (values) => {
const errors = {};
Object.keys(fieldSchemas).forEach((field) => {
try {
fieldSchemas[field].validateSync(values[field]);
} catch (e) {
errors[field] = e.message;
}
});
return errors;
};
Такой подход снижает нагрузку при частых обновлениях состояния формы.
Final Form использует индексированные пути, Yup описывает структуру
через array().of():
const schema = Yup.object({
tags: Yup.array()
.of(Yup.string().required('Значение обязательно'))
.min(1, 'Минимум один элемент')
});
Ошибки для массивов должны учитывать позицию элемента:
{
"tags[2]": "Значение обязательно"
}
Final Form хранит ошибки в виде плоского объекта, обновляемого после каждой валидации. Yup генерирует иерархическую структуру ошибок, которая требует нормализации.
Механизм обновления включает:
Yup допускает расширение через test:
const schema = Yup.object({
username: Yup.string().test(
'unique',
'Имя занято',
async (value) => {
const res = await fetch(`/check?username=${value}`);
const data = await res.json();
return data.available;
}
)
});
Такие проверки становятся частью общей цепочки валидации Final Form.
При использовании TypeScript схема Yup может быть источником типов:
import * as Yup from 'yup';
const schema = Yup.object({
age: Yup.number().required()
});
type FormValues = Yup.InferType<typeof schema>;
Final Form получает строго типизированное состояние, что снижает количество ошибок на этапе разработки.
Final Form вызывает validate перед
onSubmit. При наличии ошибок onSubmit не
выполняется.
Последовательность:
Yup позволяет описывать взаимосвязи между полями:
const schema = Yup.object({
startDate: Yup.date(),
endDate: Yup.date().min(
Yup.ref('startDate'),
'Дата окончания раньше начала'
)
});
Final Form передает актуальные значения всей формы, что обеспечивает корректную проверку зависимостей.
Final Form поддерживает parse и format,
которые изменяют данные до передачи в Yup:
<Field
name="age"
parse={(value) => Number(value)}
format={(value) => String(value)}
/>
Yup получает уже нормализованные данные, исключая необходимость дополнительных преобразований внутри схемы.