← Назад к вопросам

Как понять когда показать пользователю компонент об ошибке?

1.8 Middle🔥 132 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Когда показывать пользователю компонент об ошибке

Правильная обработка ошибок - один из ключевых аспектов разработки качественного UX. Ошибки должны быть информативными, своевременными и не мешать пользователю работать. Решение о показе ошибки зависит от типа ошибки, контекста и важности для пользователя.

Типы ошибок и когда их показывать

1. Ошибки валидации (Validation Errors) Показываем сразу после потери фокуса (blur) или при попытке отправки формы:

import { useState } from "react";

function SignUpForm() {
  const [email, setEmail] = useState("");
  const [emailError, setEmailError] = useState("");
  const [touched, setTouched] = useState(false);

  const validateEmail = (value) => {
    const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    return isValid ? "" : "Invalid email format";
  };

  const handleEmailBlur = () => {
    setTouched(true);
    const error = validateEmail(email);
    setEmailError(error);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const error = validateEmail(email);
    
    if (error) {
      setTouched(true);
      setEmailError(error);
      return;
    }
    
    // Submit form
    submitForm({ email });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        onBlur={handleEmailBlur}
        className={touched && emailError ? "border-red-500" : ""}
      />
      {touched && emailError && (
        <div className="text-red-500 text-sm mt-1" role="alert">
          {emailError}
        </div>
      )}
      <button type="submit">Sign Up</button>
    </form>
  );
}

2. Ошибки сети (Network Errors) Показываем после неудачного запроса с опцией повтора:

function ProductList() {
  const [products, setProducts] = useState([]);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const fetchProducts = async () => {
    setIsLoading(true);
    setError(null);
    
    try {
      const response = await fetch("/api/products");
      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }
      const data = await response.json();
      setProducts(data);
    } catch (err) {
      // Показываем ошибку для критических операций
      setError(err.message);
      console.error("Failed to fetch products:", err);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchProducts();
  }, []);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      {error && (
        <div className="bg-red-100 border border-red-400 text-red-700 p-4 mb-4" role="alert">
          <p className="font-bold">Failed to load products</p>
          <p>{error}</p>
          <button
            onClick={fetchProducts}
            className="mt-2 px-4 py-2 bg-red-600 text-white rounded"
          >
            Try Again
          </button>
        </div>
      )}
      {products.length > 0 && (
        <ul>
          {products.map((p) => (
            <li key={p.id}>{p.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

3. Ошибки сервера (Server Errors 5xx) Показываем с сообщением и предложением связаться с поддержкой:

function ErrorBoundary({ children }) {
  const [hasError, setHasError] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const handleError = (event) => {
      if (event.error?.status >= 500) {
        setHasError(true);
        setError(event.error);
      }
    };

    window.addEventListener("error", handleError);
    return () => window.removeEventListener("error", handleError);
  }, []);

  if (hasError) {
    return (
      <div className="flex flex-col items-center justify-center min-h-screen">
        <h1 className="text-3xl font-bold text-red-600">Oops! Something went wrong</h1>
        <p className="text-gray-600 mt-2">
          Our team has been notified. Please try again later.
        </p>
        <a
          href="/"
          className="mt-4 px-6 py-2 bg-blue-600 text-white rounded"
        >
          Go to Home
        </a>
        <p className="text-sm text-gray-500 mt-4">
          Error ID: {error?.id || "Unknown"}
        </p>
      </div>
    );
  }

  return children;
}

4. Ошибки авторизации (401/403) Показываем более деликатно - перенаправляем на логин или запрашиваем разрешение:

async function fetchProtectedData() {
  try {
    const response = await fetch("/api/protected", {
      headers: {
        "Authorization": `Bearer ${getToken()}`
      }
    });

    if (response.status === 401) {
      // Токен истёк - перенаправляем на логин
      window.location.href = "/login";
      return;
    }

    if (response.status === 403) {
      // Доступ запрещён - показываем информационное сообщение
      showNotification({
        type: "error",
        message: "You do not have permission to access this resource"
      });
      return;
    }

    const data = await response.json();
    return data;
  } catch (err) {
    console.error("Fetch error:", err);
  }
}

Правила показа ошибок

1. Показываем ошибку если:

  • Операция необходима для продолжения (валидация формы)
  • Произошла непредвиденная ошибка сервера
  • Пользователь явно инициировал действие (нажал кнопку)
  • Ошибка влияет на целостность данных

2. НЕ показываем ошибку если:

  • Это автоматическая операция фонового обновления
  • Ошибка временная и автоматически повторяется
  • Пользователь в процессе ввода данных (только после blur)
  • Это дублирование с другой ошибкой

Паттерн обработки с состояниями

const AsyncState = {
  IDLE: "idle",
  LOADING: "loading",
  SUCCESS: "success",
  ERROR: "error"
};

function DataFetcher() {
  const [state, setState] = useState(AsyncState.IDLE);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const fetchData = async () => {
    setState(AsyncState.LOADING);
    setError(null);
    
    try {
      const response = await fetch("/api/data");
      if (!response.ok) throw new Error("Failed to fetch");
      
      const result = await response.json();
      setData(result);
      setState(AsyncState.SUCCESS);
    } catch (err) {
      setError(err.message);
      setState(AsyncState.ERROR);
    }
  };

  return (
    <div>
      {state === AsyncState.LOADING && <div>Loading...</div>}
      
      {state === AsyncState.ERROR && (
        <div className="bg-red-100 p-4">
          Error: {error}
          <button onClick={fetchData}>Retry</button>
        </div>
      )}
      
      {state === AsyncState.SUCCESS && (
        <div>Data: {JSON.stringify(data)}</div>
      )}
      
      {state === AsyncState.IDLE && (
        <button onClick={fetchData}>Load Data</button>
      )}
    </div>
  );
}

Стратегии обработки разных уровней ошибок

// 1. Inline errors (рядом с полем ввода)
<input onChange={handleChange} />
{fieldErrors.email && <span className="text-red-500">{fieldErrors.email}</span>}

// 2. Toast notifications (всплывающие сообщения)
showToast({ message: "Item added to cart", type: "success" });

// 3. Modal dialogs (критические ошибки)
<Modal isOpen={showErrorModal}>
  <h2>Error</h2>
  <p>{errorMessage}</p>
  <button onClick={handleRetry}>Retry</button>
</Modal>

// 4. Page-level errors (ошибка при загрузке страницы)
{pageError && <ErrorBanner error={pageError} />}

// 5. Error boundary (неловленные ошибки)
<ErrorBoundary>
  <YourComponent />
</ErrorBoundary>

Логирование ошибок

Важно логировать все ошибки для дальнейшего анализа:

function logError(error, context) {
  // Отправляем на сервер для аналитики
  fetch("/api/logs/errors", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      message: error.message,
      stack: error.stack,
      context,
      timestamp: new Date(),
      userAgent: navigator.userAgent,
      url: window.location.href
    })
  });
}

// Используем Sentry для продакшена
import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: process.env.REACT_APP_SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.1
});

try {
  // some code
} catch (error) {
  Sentry.captureException(error);
}

Резюме

Показывайте ошибку пользователю когда:

  1. Операция необходима для продолжения работы
  2. Требуется информация от пользователя для решения проблемы
  3. Это может привести к потере данных

Не показывайте, если:

  1. Это фоновая автоматическая операция
  2. Система пытается автоматически повторить попытку
  3. Это может отвлечь пользователя от основной задачи