При работе с формами в связке YupResolver и библиотеками
управления формами (например, React Hook Form) серверные ошибки
представляют отдельный слой валидации, который не покрывается схемой
Yup. Клиентская схема отвечает за предварительную проверку данных, тогда
как сервер фиксирует бизнес-ограничения, состояние базы данных,
конкурентные изменения и правила, которые невозможно или нецелесообразно
дублировать на клиенте.
Серверные ошибки условно делятся на несколько категорий:
Ошибки полей (field-level errors) Возникают, когда сервер возвращает структуру, привязанную к конкретным полям формы:
email already existspassword is too weakusername is takenГлобальные ошибки (form-level errors) Не привязаны к конкретному полю:
Ошибки структуры запроса
Бизнес-ошибки
YupResolver выполняет синхронную или асинхронную
проверку схемы до отправки данных на сервер. Его задача — гарантировать
соответствие структуры и базовых правил:
Серверная валидация вступает в силу после отправки запроса и не может быть заменена схемой, поскольку зависит от внешнего состояния системы.
Ключевой принцип: YupResolver не обрабатывает серверные ошибки напрямую, но формирует основу для их корректного отображения в форме.
Типичный жизненный цикл данных в форме:
Пользователь вводит данные
YupResolver выполняет проверку схемы
При успехе данные отправляются на сервер
Сервер возвращает:
Ошибка преобразуется в формат формы
Ошибки отображаются через setError
Разные backend-стекы возвращают разные структуры ошибок:
{
"message": "Validation failed",
"errors": {
"email": "Email already exists",
"password": "Too weak"
}
}
{
"message": "The given data was invalid",
"errors": {
"email": ["Email already exists"],
"password": ["Too weak"]
}
}
{
"error": "conflict",
"fields": [
{ "field": "email", "message": "Taken" }
]
}
Для корректной интеграции требуется слой нормализации:
function normalizeServerErrors(response) {
const errors = response?.errors;
if (!errors) return [];
return Object.entries(errors).map(([field, message]) => ({
field,
message: Array.isArray(message) ? message[0] : message
}));
}
Альтернативный формат массива:
function normalizeFieldsArray(errorsArray) {
return errorsArray.map(err => ({
name: err.field,
type: "server",
message: err.message
}));
}
Основной механизм отображения серверных ошибок — метод
setError:
import { useForm } from "react-hook-form";
const { setError } = useForm();
async function onSubmit(data) {
try {
await api.send(data);
} catch (err) {
const normalized = normalizeServerErrors(err.response);
normalized.forEach(({ field, message }) => {
setError(field, {
type: "server",
message
});
});
}
}
Серверные ошибки не проходят через YupResolver, так как
resolver вызывается до отправки данных. Поэтому их обработка всегда
выполняется отдельно.
Ситуации, когда Yup и сервер возвращают разные ошибки для одного поля, требуют приоритизации:
Пример конфликта:
email must be validemail domain is blockedВ таких случаях серверная ошибка должна перезаписывать или дополнять клиентскую.
Хотя YupResolver в основном синхронный, Yup поддерживает
асинхронные проверки через test:
const schema = yup.object({
email: yup
.string()
.email()
.test("check-email", "Email already exists", async (value) => {
const res = await api.checkEmail(value);
return res.available;
})
});
Этот подход частично дублирует серверную проверку и может использоваться для предварительной фильтрации ошибок, но не заменяет финальную серверную валидацию.
YupResolver поддерживает передачу context,
что позволяет учитывать внешние параметры:
const resolver = yupResolver(schema, {
context: {
mode: "create"
}
});
Это используется для:
Однако серверные ошибки всё равно остаются вне этой системы и должны обрабатываться отдельно.
При работе с вложенными объектами важно сохранять путь поля:
{
"errors": {
"profile.email": "Invalid email",
"addresses[0].city": "Required"
}
}
Маппинг:
function mapNestedErrors(errors) {
return Object.entries(errors).map(([path, message]) => ({
field: path,
message
}));
}
Такая структура позволяет setError корректно привязать
сообщение к глубоко вложенным полям.
После повторной отправки формы необходимо сбрасывать серверные ошибки, чтобы не оставались устаревшие сообщения:
function clearServerErrors(fields) {
fields.forEach(field => {
clearErrors(field);
});
}
Или полностью:
clearErrors();
Наиболее стабильная интеграция достигается при фиксированном формате ошибок со стороны сервера:
errorsfield → messageБез этого слоя нормализации количество edge-case сценариев увеличивается экспоненциально.
Серверные ошибки выполняют роль финального фильтра данных, но их отображение должно быть согласовано с состоянием формы:
Для массивов и динамических форм сервер часто возвращает индексы:
{
"errors": {
"items[2].price": "Must be greater than 0"
}
}
Такая структура требует точного соответствия путей, иначе React Hook Form не сможет корректно привязать ошибку.
Не все ошибки привязаны к полям:
{
"message": "You do not have permission"
}
В таких случаях используется:
setError("root", {
type: "server",
message: "You do not have permission"
});
Или отдельное состояние:
setFormError(message);
Такой порядок обеспечивает предсказуемость поведения формы и исключает конфликт источников данных.