Доступ к ошибкам отдельных полей

В связке react-hook-form и Yup через @hookform/resolvers/yup механизм обработки ошибок строится вокруг преобразования ValidationError в объект formState.errors. Основная задача резолвера — сопоставить ошибки схемы валидации с конкретными полями формы по их путям (path) и обеспечить удобный доступ к ним на уровне UI.

Каждая ошибка, возвращаемая через YupResolver, имеет строгую привязку к структуре данных формы. Это означает, что доступ к ошибкам отдельных полей всегда осуществляется через ключи, соответствующие структуре объекта формы.


Общая форма объекта ошибок

После выполнения валидации YupResolver формирует объект:

formState.errors

Его структура повторяет структуру формы:

{
  name: { message: "Обязательное поле", type: "required" },
  email: { message: "Неверный email", type: "email" },
  profile: {
    firstName: { message: "Слишком короткое имя", type: "min" }
  }
}

Ключевой принцип: каждое поле получает собственный объект ошибки, если валидация для него не прошла.


Принцип сопоставления Yup → errors

Yup формирует ошибку в виде дерева ValidationError, где каждое нарушение содержит:

  • path — путь к полю (profile.firstName)
  • message — текст ошибки
  • type — тип нарушения (если задано через yup тесты)

YupResolver преобразует это дерево в плоскую структуру, но сохраняет вложенность объектов, соответствующую путям.

Пример схемы:

const schema = yup.object({
  profile: yup.object({
    firstName: yup.string().required(),
    lastName: yup.string().min(3)
  })
});

Результат ошибок:

errors.profile.firstName.message
errors.profile.lastName.message

Доступ к ошибкам простых полей

Для плоских форм доступ осуществляется напрямую через имя поля:

errors.email?.message
errors.password?.message

Использование optional chaining обязательно, поскольку до момента возникновения ошибки поле может отсутствовать в объекте.

Типичный шаблон:

const emailError = formState.errors.email?.message;

Доступ к вложенным объектам

При работе с объектными структурами форма становится иерархической:

{
  user: {
    address: {
      city: string
    }
  }
}

Ошибки будут иметь идентичную вложенность:

errors.user?.address?.city?.message

Ключевой момент: каждый уровень вложенности может отсутствовать, если валидация не сработала для этого пути.


Доступ к ошибкам массивов

При использовании yup.array() структура ошибок индексируется:

{
  users: [
    { name: "Ошибка" },
    { name: "Ошибка" }
  ]
}

Доступ:

errors.users?.[0]?.name?.message
errors.users?.[1]?.name?.message

Для динамических списков это основной способ адресации ошибок.

Особенность: при частичной валидации массива ошибки могут существовать только для отдельных индексов, что требует проверки через optional chaining на каждом уровне.


Динамические поля и безопасный доступ

При работе с динамическими ключами (например, fields.map) прямой доступ через точечную нотацию невозможен. Используется индексирование:

errors.items?.[index]?.title?.message

Или универсальный доступ через строковый путь:

const error = errors?.[fieldName]?.message;

Однако такой подход применим только для плоских структур. Для вложенных объектов требуется разбор пути.


Использование lodash/get для универсального доступа

При глубокой вложенности и динамических путях часто используется get:

import get from "lodash/get";

const message = get(errors, "user.address.city.message");

Это позволяет избежать длинных цепочек ?..

Важно учитывать, что структура errors содержит объекты с полем message, поэтому путь должен оканчиваться на .message.


Типизация ошибок в TypeScript

В TypeScript структура errors зависит от типа формы:

type FormValues = {
  user: {
    name: string;
  };
};

useForm<FormValues> формирует:

FieldErrors<FormValues>

Доступ:

errors.user?.name?.message

Типизация обеспечивает автодополнение путей, но не исключает необходимость проверки undefined.


Отличие ошибок Yup от стандартной валидации

YupResolver отличается тем, что:

  • ошибки агрегируются из всей схемы сразу
  • отсутствуют промежуточные состояния
  • структура строго соответствует схеме Yup
  • ошибки не хранятся в плоском списке

Это влияет на доступ к полям: нельзя ожидать массива ошибок, только дерево.


Приоритет ошибок при множественных нарушениях

Yup по умолчанию возвращает первую ошибку для поля, если не включен abortEarly: false.

При конфигурации:

yup.object().shape(
  {
    email: yup.string().required().email()
  },
  { abortEarly: false }
)

структура может содержать несколько ошибок, но react-hook-form обычно отображает только первую:

errors.email?.message

Проверка наличия ошибки поля

Перед отображением ошибки используется проверка:

if (errors.password) {
  // поле невалидно
}

или более точно:

if (errors.password?.message) {
  // есть сообщение ошибки
}

Это важно для отделения состояния “ошибка существует” от “ошибка содержит текст”.


Работа с вложенными union-структурами

При сложных формах:

{
  order: {
    items: [
      {
        product: {
          title: string
        }
      }
    ]
  }
}

доступ к ошибке:

errors.order?.items?.[i]?.product?.title?.message

Каждый сегмент пути соответствует уровню вложенности Yup-схемы.


Особенности отсутствующих узлов в errors

Объект errors не содержит пустых веток. Это означает:

  • отсутствующее поле = undefined
  • наличие ошибки = объект с type, message, ref

Следовательно, обращение без проверки приводит к runtime-ошибкам:

errors.user.address.city.message // небезопасно

Правильный вариант:

errors.user?.address?.city?.message

Использование ref и привязка к DOM

Каждая ошибка может содержать ref:

errors.email.ref

Это используется для:

  • фокусировки поля
  • интеграции с DOM-элементом
  • автоматического скролла к ошибке

Хотя ref редко используется напрямую, он сохраняется в структуре ошибок и может быть полезен при кастомной обработке UI.


Унификация доступа в UI-слое

В типичной архитектуре UI доступ к ошибкам абстрагируется:

const getError = (errors, name) => errors?.[name]?.message;

или для вложенных структур:

const getNestedError = (errors, path) => get(errors, `${path}.message`);

Это снижает связность компонентов с конкретной структурой формы.


Поведение при частичной валидации

При изменении одного поля YupResolver пересчитывает всю схему, но обновляет только затронутые ветки:

  • ошибки других полей сохраняются
  • обновляется только изменённая часть дерева
  • структура errors остается согласованной с формой

Это важно при динамических формах с частыми изменениями значений.


Ключевые особенности доступа к ошибкам

  • структура всегда повторяет форму данных
  • доступ осуществляется через безопасную навигацию по пути
  • массивы требуют индексного обращения
  • вложенные объекты требуют цепочек ?.
  • универсальный доступ возможен через lodash/get
  • TypeScript обеспечивает частичную защиту, но не заменяет проверки