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

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


Организация структуры контроллера

Для работы с серверной валидацией создается Stimulus-контроллер, привязанный к форме. Классический подход включает следующие элементы:

  • Targets: поля формы, контейнер для ошибок, кнопка отправки.
  • Values: настройки, например URL для отправки данных и метод запроса.
  • Actions: обработка событий submit, input или change.

Пример структуры контроллера:

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static targets = ["form", "submit", "field", "errors"];
  static values = { url: String, method: String };

  submit(event) {
    event.preventDefault();
    this.clearErrors();
    this.sendData();
  }

  sendData() {
    const formData = new FormData(this.formTarget);

    fetch(this.urlValue, {
      method: this.methodValue || "POST",
      body: formData,
      headers: {
        "Accept": "application/json"
      }
    })
      .then(response => response.json())
      .then(data => this.handleResponse(data))
      .catch(error => console.error("Network error:", error));
  }

  handleResponse(data) {
    if (data.errors) {
      this.showErrors(data.errors);
    } else {
      this.formTarget.reset();
    }
  }

  showErrors(errors) {
    for (const [fieldName, messages] of Object.entries(errors)) {
      const field = this.fieldTargets.find(f => f.name === fieldName);
      if (field) {
        const errorContainer = document.createElement("div");
        errorContainer.classList.add("field-error");
        errorContainer.textContent = messages.join(", ");
        field.after(errorContainer);
      }
    }
  }

  clearErrors() {
    this.errorsTargets.forEach(el => el.remove());
  }
}

Привязка к HTML

Форма должна содержать data-controller, data-target и при необходимости data-action:

<form data-controller="validation"
      data-validation-url-value="/users"
      data-validation-method-value="POST"
      data-action="submit->validation#submit">

  <input type="text" name="username" data-validation-target="field">
  <input type="email" name="email" data-validation-target="field">
  <button type="submit" data-validation-target="submit">Отправить</button>
</form>

Контейнеры для ошибок создаются динамически контроллером после получения ответа сервера. Для удобства можно добавить глобальный контейнер data-validation-target="errors", куда будут выводиться все сообщения.


Асинхронная обработка и UX

Использование fetch с Promise позволяет:

  • Отображать состояние загрузки кнопки отправки.
  • Деактивировать кнопку до завершения запроса.
  • Предоставлять пользователю мгновенный фидбэк по полям без перезагрузки страницы.

Пример управления состоянием кнопки:

submit(event) {
  event.preventDefault();
  this.submitTarget.disabled = true;
  this.clearErrors();
  this.sendData();
}

handleResponse(data) {
  this.submitTarget.disabled = false;
  if (data.errors) {
    this.showErrors(data.errors);
  } else {
    this.formTarget.reset();
  }
}

Обработка ошибок сервера и нестандартных форматов

Серверная валидация может возвращать ошибки в разных форматах. Stimulus-контроллер должен быть готов к:

  • Структурированным JSON-ответам вида { errors: { field: ["message"] } }.
  • Простым текстовым ошибкам (500 Internal Server Error), которые можно отобразить в глобальном контейнере.
  • Сетевым ошибкам — использование блока catch с выводом уведомления о недоступности сервера.

Совмещение с клиентской валидацией

Для улучшения UX рекомендуется комбинировать серверную и клиентскую валидацию:

  • Клиентская проверка обязательных полей и формата данных.
  • Серверная проверка уникальности, логики бизнес-правил и сложных зависимостей.
  • Отображение ошибок в едином стиле, используя один набор элементов DOM.

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


Резюме ключевых практик

  • Каждый контроллер должен управлять своим набором полей и ошибок через targets.
  • Использование FormData и fetch обеспечивает удобный асинхронный обмен данными.
  • Ошибки сервера следует преобразовывать в читаемый формат и выводить рядом с соответствующим полем.
  • Управление состоянием кнопок и форм повышает удобство взаимодействия.
  • Комбинация клиентской и серверной валидации позволяет сократить количество ошибок и нагрузку на сервер.