Отображение ошибок валидации

Валидация входных данных в JavaScript-проектах с использованием Validator.js чаще всего строится вокруг идеи явного накопления ошибок. Библиотека предоставляет набор чистых функций, возвращающих true или false, поэтому механизм ошибок полностью реализуется на стороне приложения.

Типовая модель хранения ошибок представляет собой объект, где ключи соответствуют именам полей, а значения — массивы сообщений:

const errors = {
  email: ["Некорректный email адрес"],
  password: ["Пароль слишком короткий", "Пароль должен содержать цифры"]
};

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


Базовый паттерн формирования ошибок

При использовании Validator.js логика проверки обычно выстраивается последовательно, с явным добавлением сообщений при несоответствии условиям:

import validator from "validator";

function validateUser(data) {
  const errors = {};

  if (!validator.isEmail(data.email || "")) {
    errors.email = errors.email || [];
    errors.email.push("Email не соответствует формату");
  }

  if (!validator.isLength(data.password || "", { min: 8 })) {
    errors.password = errors.password || [];
    errors.password.push("Минимальная длина пароля — 8 символов");
  }

  return errors;
}

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


Нормализация ошибок для интерфейса

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

function normalizeErrors(errors) {
  const result = {};

  Object.keys(errors).forEach((field) => {
    result[field] = errors[field][0];
  });

  return result;
}

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

Альтернативный подход — объединение всех сообщений в одну строку:

function joinErrors(errors) {
  const result = {};

  Object.keys(errors).forEach((field) => {
    result[field] = errors[field].join(". ");
  });

  return result;
}

Отображение ошибок в пользовательском интерфейсе

На стороне интерфейса ошибки обычно связываются с конкретными полями формы. В DOM-структурах это реализуется через идентификаторы или data-атрибуты:

function renderErrors(errors) {
  Object.keys(errors).forEach((field) => {
    const errorContainer = document.querySelector(`[data-error="${field}"]`);

    if (errorContainer) {
      errorContainer.textContent = errors[field][0];
    }
  });
}

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


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

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

function clearErrors() {
  document.querySelectorAll("[data-error]").forEach((el) => {
    el.textContent = "";
  });
}

На уровне структуры данных аналогично создаётся новый объект ошибок или очищаются существующие поля:

function resetErrors(errors) {
  Object.keys(errors).forEach((key) => delete errors[key]);
}

Условное отображение ошибок

Ошибки не всегда отображаются сразу после первой проверки. Распространённый подход — показывать их только после попытки отправки формы:

let touched = false;

function handleSubmit(data) {
  touched = true;

  const errors = validateUser(data);

  if (Object.keys(errors).length > 0) {
    renderErrors(errors);
    return;
  }

  submitForm(data);
}

Дополнительно может использоваться проверка touched для каждого поля, что позволяет отображать ошибки только после взаимодействия с ним.


Группировка и приоритет ошибок

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

if (validator.isEmpty(data.email || "")) {
  errors.email = ["Email обязателен"];
} else if (!validator.isEmail(data.email)) {
  errors.email = ["Некорректный формат email"];
}

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


Интеграция с асинхронной валидацией

Некоторые проверки требуют обращения к серверу, например, проверка уникальности email. В таких случаях ошибки добавляются асинхронно:

async function validateEmailUniqueness(email, errors) {
  const exists = await api.checkEmail(email);

  if (exists) {
    errors.email = errors.email || [];
    errors.email.push("Email уже используется");
  }
}

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


Структуры ошибок для сложных форм

В формах с вложенными объектами (например, адреса или профили) ошибки часто повторяют структуру данных:

const errors = {
  user: {
    name: ["Имя обязательно"],
    address: {
      city: ["Город не указан"]
    }
  }
};

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

function renderNestedErrors(errors, prefix = "") {
  Object.keys(errors).forEach((key) => {
    const value = errors[key];

    if (Array.isArray(value)) {
      const el = document.querySelector(`[data-error="${prefix}${key}"]`);
      if (el) el.textContent = value[0];
    } else {
      renderNestedErrors(value, `${prefix}${key}.`);
    }
  });
}

Унификация сообщений об ошибках

Для обеспечения единообразия интерфейса часто применяется централизованный словарь сообщений:

const messages = {
  required: "Поле обязательно для заполнения",
  email: "Некорректный email",
  minLength: (min) => `Минимальная длина ${min} символов`
};

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

if (validator.isEmpty(password)) {
  errors.password = [messages.required];
}

if (!validator.isLength(password, { min: 8 })) {
  errors.password.push(messages.minLength(8));
}

Представление ошибок в различных слоях приложения

В архитектуре клиент-сервер ошибки проходят несколько этапов преобразования:

  1. Валидация на клиенте — формирование базовых сообщений
  2. Серверная проверка — уточнение бизнес-логики
  3. Транспортировка — передача через JSON
  4. Отображение — адаптация под интерфейс

Пример серверного ответа:

{
  "errors": {
    "email": ["Email уже зарегистрирован"]
  }
}

Фронтенд адаптирует структуру без изменения семантики сообщений.


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

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

function createError(field, message) {
  return {
    field,
    message,
    timestamp: Date.now()
  };
}

Такая модель упрощает логирование и отладку, особенно при сложных сценариях валидации.


Динамическое обновление ошибок

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

function handleChange(field, value) {
  const errors = validateField(field, value);
  renderErrors(errors);
}

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


Оптимизация отображения при большом количестве ошибок

При значительном числе ошибок важно избегать перегрузки интерфейса. Используются стратегии:

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

Такая фильтрация снижает когнитивную нагрузку и улучшает читаемость интерфейса.


Хранение состояния ошибок

В современных SPA-приложениях ошибки часто хранятся в состоянии компонента или глобальном сторе:

const state = {
  errors: {}
};

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