Серверная валидация

Radix UI — это библиотека компонентов интерфейса для React, которая предоставляет низкоуровневые, полностью управляемые и доступные элементы, готовые для кастомизации. В контексте форм и ввода данных важным аспектом является серверная валидация, когда проверка данных выполняется не только на клиенте, но и на сервере для обеспечения безопасности и целостности данных.


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

Клиентская валидация выполняется в браузере, обычно с использованием HTML-атрибутов (required, pattern) или JavaScript-логики. Она обеспечивает быстрый отклик и улучшает UX, но не гарантирует безопасность, поскольку её можно обойти.

Серверная валидация выполняется на стороне сервера после отправки формы и является обязательной для критичных данных: аутентификация, финансовые транзакции, уникальные идентификаторы, данные пользователей.

Ключевые преимущества серверной валидации:

  • Защита от подделки запросов (CSRF, XSS).
  • Целостность данных в базе.
  • Унификация логики в многоуровневых приложениях (клиент + сервер).

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

Radix UI предоставляет низкоуровневые компоненты, которые не включают собственную логику валидации, что позволяет интегрировать серверную проверку через обработку ошибок после submit.

Пример: форма с использованием @radix-ui/react-form

import * as Form from '@radix-ui/react-form';
import { useState } from 'react';

function UserForm() {
  const [serverErrors, setServerErrors] = useState({});

  const handleSubmit = async (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const payload = Object.fromEntries(formData);

    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
      const result = await response.json();

      if (!response.ok) {
        setServerErrors(result.errors);
      } else {
        setServerErrors({});
        console.log('Данные успешно отправлены');
      }
    } catch (err) {
      console.error('Ошибка сервера', err);
    }
  };

  return (
    <Form.Root onSub mit={handleSubmit}>
      <Form.Field name="username">
        <Form.Label>Имя пользователя</Form.Label>
        <Form.Control asChild>
          <input type="text" />
        </Form.Control>
        {serverErrors.username && (
          <Form.Message match="valueMissing">{serverErrors.username}</Form.Message>
        )}
      </Form.Field>

      <Form.Field name="email">
        <Form.Label>Email</Form.Label>
        <Form.Control asChild>
          <input type="email" />
        </Form.Control>
        {serverErrors.email && (
          <Form.Message match="typeMismatch">{serverErrors.email}</Form.Message>
        )}
      </Form.Field>

      <Form.Submit>Отправить</Form.Submit>
    </Form.Root>
  );
}

В этом примере:

  • Form.Root управляет сабмитом формы.
  • Form.Field и Form.Control связывают поле с именем и контролом.
  • Form.Message отображает сообщения об ошибках, включая серверные.

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


Обработка асинхронных ошибок

Серверная валидация часто возвращает асинхронные ошибки:

  • Проверка уникальности имени пользователя
  • Валидация токенов
  • Ограничения по паролю

Для таких случаев используется state для ошибок и асинхронная функция submit. Radix UI не блокирует рендер сообщений, поэтому можно динамически обновлять Form.Message.

Пример обработки уникального имени пользователя:

if (result.errors.username === 'already_exists') {
  setServerErrors((prev) => ({
    ...prev,
    username: 'Пользователь с таким именем уже существует',
  }));
}

Стилизация ошибок и доступность

Radix UI предоставляет доступные для экранных читалок сообщения. Важные моменты:

  • Использовать Form.Message с атрибутом match, даже если сообщение приходит с сервера.
  • Цвет и визуальное выделение ошибок можно задавать через CSS, но важно сохранять доступность (например, через aria-invalid="true").
  • Можно комбинировать клиентскую и серверную проверку: сначала быстрый клиентский check, затем серверная проверка при сабмите.

Паттерны организации серверной валидации

  1. Объект ошибок по полям

    • Каждое поле формы получает свой ключ с текстом ошибки.
    • Удобно интегрируется с Radix UI Form.Message.
  2. Централизованная обработка ошибок API

    • Сервер возвращает структурированный JSON с массивом ошибок:

      {
        "errors": {
          "email": "Email уже зарегистрирован",
          "password": "Слишком короткий пароль"
        }
      }
    • Этот объект маппится на поля формы.

  3. Комбинация с useForm библиотекой

    • Radix UI может использоваться как view layer, а react-hook-form или formik управляют логикой валидации и серверными ошибками.

Практические рекомендации

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

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