Вывод ошибок в интерфейсе

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

YupResolver преобразует результат проверки схемы Yup в объект, совместимый с механизмом управления формами (чаще всего React Hook Form). Основная структура ошибок имеет вид:

{
  fieldName: {
    type: "validationType",
    message: "Текст ошибки"
  }
}

Для вложенных объектов структура становится иерархической:

{
  user: {
    email: {
      message: "Некорректный email",
      type: "email"
    }
  }
}

Массивы формируют индексированную структуру:

{
  users: [
    {
      email: {
        message: "Обязательное поле",
        type: "required"
      }
    }
  ]
}

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

Доступ к ошибкам в UI-слое

В типичной интеграции с React Hook Form ошибки извлекаются через formState.errors:

const {
  register,
  formState: { errors }
} = useForm({
  resolver: yupResolver(schema)
});

Каждое поле получает доступ к собственной ошибке напрямую по ключу:

<input {...register("email")} />
<p>{errors.email?.message}</p>

Ключевой момент заключается в том, что отсутствие ошибки выражается как undefined, а не как пустая строка или объект. Это влияет на логику рендера: любое условное отображение должно проверять наличие поля через optional chaining или явное сравнение.

Отображение ошибок на уровне отдельных полей

Наиболее распространённая модель интерфейса — привязка ошибки к конкретному input-компоненту.

const EmailField = ({ register, error }) => (
  <div>
    <input {...register("email")} />
    {error && <span>{error.message}</span>}
  </div>
);

Здесь важно учитывать, что error.message является финальным текстом, сформированным Yup. Это означает, что вся логика генерации текста отделена от UI, а компонент лишь отображает результат.

Визуальное состояние поля

Ошибки часто используются не только для вывода текста, но и для изменения состояния элемента:

<input
  {...register("email")}
  className={errors.email ? "input error" : "input"}
/>

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

Вложенные поля и безопасный доступ

При работе с объектами и массивами доступ к ошибкам становится менее прямым. Например:

errors.user?.address?.street?.message

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

const getError = (errors, path) =>
  path.split(".").reduce((acc, key) => acc?.[key], errors);

Использование подобных функций позволяет унифицировать отображение ошибок в повторно используемых компонентах.

Отображение ошибок для массивов

Для динамических списков ключевым становится индекс элемента:

users.map((user, index) => (
  <div key={user.id}>
    <input {...register(`users.${index}.email`)} />
    <p>{errors.users?.[index]?.email?.message}</p>
  </div>
));

Ошибка всегда привязана к конкретному индексу массива, что требует стабильной идентификации элементов списка. Использование индекса как ключа допустимо только при отсутствии перестановок элементов.

Глобальные и агрегированные ошибки

Помимо полей, YupResolver может возвращать ошибки уровня всей формы. Такие ошибки часто применяются для бизнес-логики:

{
  root: {
    message: "Невозможно сохранить форму",
    type: "server"
  }
}

В React Hook Form это обычно представлено как:

errors.root?.message

Их отображение располагается вне контекста отдельных полей:

{errors.root && (
  <div className="form-error">
    {errors.root.message}
  </div>
)}

Состояния touched и dirty как фильтр отображения

Ошибки не всегда отображаются сразу. Часто используется логика:

  • touchedFields — поле было в фокусе
  • dirtyFields — поле изменено
{errors.email && touchedFields.email && (
  <span>{errors.email.message}</span>
)}

Это снижает визуальный шум при первичном рендере формы и делает интерфейс более предсказуемым.

Приоритет отображаемых ошибок

Yup может возвращать несколько нарушений, но обычно отображается только первая ошибка на поле. Это связано с режимом валидации (abortEarly):

yup.string().required().min(5)

При abortEarly: true будет возвращена только первая ошибка. При отключении:

yup.string().required().min(5).trim()

можно получить массив ошибок, но YupResolver в стандартной интеграции агрегирует их в одно сообщение. Поэтому UI-слой оперирует уже финализированным текстом.

Кастомизация сообщений для интерфейса

Сообщения формируются в схеме Yup:

const schema = yup.object({
  email: yup
    .string()
    .email("Введите корректный email")
    .required("Email обязателен")
});

UI не должен изменять текст ошибки, но может форматировать его отображение:

<span className="error-text">
  {errors.email?.message?.toUpperCase()}
</span>

При этом важно сохранять неизменной семантику сообщения, так как она может использоваться для логирования и аналитики.

Очистка ошибок и ручное управление состоянием

Ошибки могут быть сброшены через clearErrors:

clearErrors("email");

или полностью:

clearErrors();

Это влияет на интерфейс мгновенно, поэтому очистка часто синхронизируется с пользовательскими действиями:

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

Программная установка ошибок

Помимо YupResolver, ошибки могут задаваться вручную:

setError("email", {
  type: "manual",
  message: "Ошибка сервера"
});

Такие ошибки отображаются тем же способом, что и валидированные, что обеспечивает единый механизм UI-рендеринга независимо от источника ошибки.

Асинхронные ошибки и серверная валидация

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

{
  email: "уже существует",
  password: "слишком слабый"
}

Их трансформация:

Object.entries(serverErrors).forEach(([field, message]) => {
  setError(field, {
    type: "server",
    message
  });
});

UI при этом не различает источник ошибки, если не используется дополнительная метка type.

Доступность и семантика ошибок

Корректный вывод ошибок включает привязку к accessibility-атрибутам:

<input
  {...register("email")}
  aria-invalid={!!errors.email}
  aria-describedby="email-error"
/>

<span id="email-error">
  {errors.email?.message}
</span>

Это обеспечивает корректное поведение скринридеров и улучшает навигацию по форме.

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

Частое обновление formState.errors может вызывать лишние ререндеры. Оптимизация достигается через:

  • использование useFormState
  • мемоизацию компонентов полей
  • изоляцию подписки на конкретные поля
const { errors } = useFormState({
  control,
  name: "email"
});

Такой подход ограничивает перерендер только теми компонентами, которые зависят от конкретного поля.

Форматирование и локализация ошибок

В многоязычных интерфейсах Yup может использоваться с функцией трансляции:

yup.setLocale({
  required: "Обязательное поле",
  email: "Неверный формат email"
});

Это централизует контроль текстов и исключает необходимость локализации на уровне UI.

При этом UI остаётся нейтральным:

<span>{errors.email?.message}</span>

Конфликт состояний: ошибка vs пользовательский ввод

При вводе нового значения ошибка может сохраняться до повторной валидации. Типичный сценарий:

  1. Поле невалидно
  2. Пользователь вводит новое значение
  3. Ошибка визуально остаётся до триггера валидации

Решение заключается в стратегии режима валидации:

  • onChange
  • onBlur
  • onSubmit
useForm({
  resolver: yupResolver(schema),
  mode: "onChange"
});

Выбор режима напрямую влияет на то, когда интерфейс обновляет состояние ошибок.

Унификация отображения ошибок в дизайн-системах

В крупных интерфейсах применяется единый компонент:

const FieldError = ({ error }) =>
  error ? <span className="error">{error.message}</span> : null;

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


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