Серверные ошибки

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


Обработка ошибок при асинхронных действиях

Компоненты Radix UI, такие как Dialog, Toast, Popover и Form, часто используются для отображения данных после запроса к серверу. Важным моментом является правильное управление состоянием ошибки.

Пример паттерна обработки ошибки:

import * as Toast from '@radix-ui/react-toast';
import { useState } from 'react';

function SaveButton({ apiCall }) {
  const [error, setError] = useState(null);

  const handleSave = async () => {
    try {
      await apiCall();
      setError(null);
    } catch (err) {
      setError(err.message || 'Произошла ошибка при сохранении');
    }
  };

  return (
    <>
      <button onCl ick={handleSave}>Сохранить</button>
      {error && (
        <Toast.Root>
          <Toast.Title>Ошибка</Toast.Title>
          <Toast.Description>{error}</Toast.Description>
        </Toast.Root>
      )}
    </>
  );
}

В этом примере используется компонент Toast для визуального уведомления о серверной ошибке. Ключевой момент — хранение состояния ошибки в useState и динамическое отображение уведомления только при наличии ошибки.


Интеграция с формами

Radix UI предоставляет низкоуровневые компоненты для форм (Label, Input, Field, Form), которые можно интегрировать с серверной валидацией.

Паттерн обработки ошибок в форме:

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

function ProfileForm({ saveProfile }) {
  const { register, handleSubmit, setError } = useForm();

  const onSub mit = async (data) => {
    try {
      await saveProfile(data);
    } catch (err) {
      if (err.fieldErrors) {
        Object.entries(err.fieldErrors).forEach(([field, message]) => {
          setError(field, { type: 'server', message });
        });
      }
    }
  };

  return (
    <Form.Root onSub mit={handleSubmit(onSubmit)}>
      <Form.Field name="username">
        <Form.Label>Имя пользователя</Form.Label>
        <Form.Control asChild>
          <input {...register('username')} />
        </Form.Control>
        <Form.Message match="valueMissing">Поле обязательно</Form.Message>
        <Form.Message match="typeMismatch">{/* Ошибка сервера */}</Form.Message>
      </Form.Field>
      <button type="submit">Сохранить</button>
    </Form.Root>
  );
}

Здесь Radix UI Form.Message используется для отображения как клиентских, так и серверных ошибок, что позволяет пользователю сразу видеть, какое поле содержит некорректные данные после запроса на сервер.


Централизованная обработка ошибок

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

import { createContext, useContext, useState } from 'react';
import * as Toast from '@radix-ui/react-toast';

const ErrorContext = createContext();

export const ErrorProvider = ({ children }) => {
  const [error, setError] = useState(null);

  return (
    <ErrorContext.Provider value={{ setError }}>
      {children}
      {error && (
        <Toast.Root>
          <Toast.Title>Ошибка</Toast.Title>
          <Toast.Description>{error}</Toast.Description>
        </Toast.Root>
      )}
    </ErrorContext.Provider>
  );
};

export const useError = () => useContext(ErrorContext);

Любой компонент может использовать useError для отображения ошибок, что позволяет избежать дублирования логики в каждом компоненте Radix UI.


Особенности работы с асинхронными компонентами

Некоторые компоненты, такие как Dialog или Popover, часто используют динамический контент с сервера. В таких случаях важно:

  • Показывать индикатор загрузки (Spinner, Progress) до завершения запроса.
  • Обрабатывать ошибки сразу после получения ответа.
  • Не рендерить компонент с некорректными данными, чтобы избежать неконсистентного состояния UI.

Пример с Dialog:

import * as Dialog from '@radix-ui/react-dialog';
import { useState, useEffect } from 'react';

function UserDialog({ userId, fetchUser }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .catch((err) => setError(err.message));
  }, [userId]);

  return (
    <Dialog.Root>
      <Dialog.Trigger>Открыть профиль</Dialog.Trigger>
      <Dialog.Content>
        {error && <div>Ошибка: {error}</div>}
        {!error && !user && <div>Загрузка...</div>}
        {user && <div>Имя: {user.name}</div>}
      </Dialog.Content>
    </Dialog.Root>
  );
}

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


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

  • Использовать Toast для кратких уведомлений об ошибках, Form.Message — для ошибок полей формы.
  • Сохранять состояние ошибки в компоненте или в контексте для глобальной доступности.
  • Разграничивать клиентские и серверные ошибки.
  • Всегда отображать индикаторы загрузки при асинхронных запросах.
  • Не рендерить контент до получения корректного ответа с сервера.

Эти практики обеспечивают чистый, доступный и предсказуемый интерфейс при работе с Radix UI и серверными данными.