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

Как отловить и показать на экране ошибку в React?

2.3 Middle🔥 221 комментариев
#React

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

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

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

Как отловить и показать на экране ошибку в React

Типы ошибок в React

  1. Render ошибки — во время рендеринга компонента
  2. Event handler ошибки — в обработчиках событий (onClick, onChange)
  3. Async ошибки — в async/await и Promises
  4. Lifecycle ошибки — в useEffect и других хуках

Способ 1: Error Boundaries (class компоненты)

Error Boundary — это класс-компонент, который ловит ошибки в дочерних компонентах.

import React from "react";

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    // Обновить state чтобы следующий render показал fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Залогировать ошибку
    console.error("Error caught:", error, errorInfo);
    this.setState({
      error: error,
      errorInfo: errorInfo
    });
    
    // Можно отправить ошибку на сервер
    logErrorToServer(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{
          padding: "20px",
          backgroundColor: "#fee",
          border: "1px solid #f00",
          borderRadius: "4px"
        }}>
          <h2>Что-то пошло не так</h2>
          <p>{this.state.error && this.state.error.toString()}</p>
          <details style={{ whiteSpace: "pre-wrap", marginTop: "10px" }}>
            {this.state.errorInfo && this.state.errorInfo.componentStack}
          </details>
          <button onClick={() => window.location.reload()}>
            Перезагрузить страницу
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Использование
<ErrorBoundary>
  <App />
</ErrorBoundary>

Что ловит Error Boundary:

  • Ошибки в render методах
  • Ошибки в lifecycle методах (constructor, componentDidMount, etc)
  • Ошибки в построении дочерних компонентов

Что НЕ ловит:

  • Event handlers (используй try-catch)
  • Async код (setTimeout, Promises)
  • Код самого Error Boundary
  • Ошибки на сервере (SSR)

Способ 2: Try-Catch в event handlers

Error Boundaries не ловят ошибки в event handlers. Нужно использовать обычный try-catch.

function UserForm() {
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError(null); // Очистить старые ошибки
    
    try {
      const response = await fetch("/api/users", {
        method: "POST",
        body: JSON.stringify({ name: "Alice" })
      });
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await response.json();
      console.log("Success:", data);
      // Показать успех пользователю
    } catch (error) {
      console.error("Error:", error);
      setError(error.message); // Показать ошибку
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input type="text" placeholder="Имя" />
        <button type="submit">Отправить</button>
      </form>
      
      {error && (
        <div style={{
          padding: "10px",
          backgroundColor: "#fee",
          color: "#c00",
          marginTop: "10px",
          borderRadius: "4px"
        }}>
          Ошибка: {error}
        </div>
      )}
    </div>
  );
}

Способ 3: Ловля ошибок в useEffect

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let isMounted = true; // Для отмены setState после unmount

    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch("/api/data");
        
        if (!response.ok) {
          throw new Error("Failed to fetch");
        }
        
        const json = await response.json();
        
        if (isMounted) {
          setData(json);
          setError(null);
        }
      } catch (err) {
        if (isMounted) {
          setError(err.message);
          setData(null);
        }
      } finally {
        if (isMounted) {
          setLoading(false);
        }
      }
    };

    fetchData();

    return () => {
      isMounted = false; // Cleanup
    };
  }, []);

  if (loading) return <p>Загрузка...</p>;
  if (error) return <p style={{ color: "red" }}>Ошибка: {error}</p>;
  
  return <div>{JSON.stringify(data)}</div>;
}

Способ 4: Global error handler + Error Boundary

Комбинированный подход для production:

// 1. Error service — логирование ошибок
class ErrorService {
  static log(error, context = {}) {
    const errorData = {
      message: error.message,
      stack: error.stack,
      timestamp: new Date().toISOString(),
      context: context,
      userAgent: navigator.userAgent
    };
    
    // Отправить на сервер (Sentry, LogRocket, etc)
    console.error("Error logged:", errorData);
    fetch("/api/logs/errors", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(errorData)
    }).catch(console.error);
  }
}

// 2. Error Boundary с логированием
class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    ErrorService.log(error, { type: "render", componentStack: errorInfo.componentStack });
  }
  // ... остальной код
}

// 3. Global handler для uncaught errors
window.addEventListener("error", (event) => {
  ErrorService.log(event.error, { type: "global" });
});

// 4. Handler для unhandled rejections (Promise)
window.addEventListener("unhandledrejection", (event) => {
  ErrorService.log(event.reason, { type: "unhandledRejection" });
});

// 5. Wrap async handlers
function withErrorHandler(asyncFn) {
  return async (...args) => {
    try {
      return await asyncFn(...args);
    } catch (error) {
      ErrorService.log(error, { type: "eventHandler" });
      throw error; // Пробросить дальше, если нужно
    }
  };
}

// Использование
const handleClick = withErrorHandler(async () => {
  const response = await fetch("/api/data");
  if (!response.ok) throw new Error("Request failed");
  return response.json();
});

Способ 5: Custom Error UI компонент

function ErrorAlert({ error, onDismiss, retry }) {
  if (!error) return null;

  return (
    <div style={{
      padding: "16px",
      backgroundColor: "#fee",
      border: "1px solid #f00",
      borderRadius: "4px",
      marginBottom: "16px",
      display: "flex",
      justifyContent: "space-between",
      alignItems: "center"
    }}>
      <div>
        <strong>Ошибка:</strong> {error.message || "Что-то пошло не так"}
      </div>
      <div>
        {retry && (
          <button 
            onClick={retry}
            style={{ marginRight: "8px", cursor: "pointer" }}
          >
            Повторить
          </button>
        )}
        {onDismiss && (
          <button 
            onClick={onDismiss}
            style={{ cursor: "pointer" }}
          >
            Закрыть
          </button>
        )}
      </div>
    </div>
  );
}

// Использование
function App() {
  const [error, setError] = useState(null);

  const handleRetry = async () => {
    try {
      // Повторить запрос
      await fetch("/api/data");
      setError(null);
    } catch (err) {
      setError(err);
    }
  };

  return (
    <div>
      <ErrorAlert 
        error={error}
        onDismiss={() => setError(null)}
        retry={handleRetry}
      />
      {/* Остальной контент */}
    </div>
  );
}

Практический пример: Полный обработчик ошибок

import React, { useState } from "react";

function LoginForm() {
  const [formState, setFormState] = useState({
    email: "",
    password: "",
    loading: false,
    error: null,
    success: false
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    setFormState(prev => ({ ...prev, loading: true, error: null }));

    try {
      const response = await fetch("/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: formState.email,
          password: formState.password
        })
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || "Login failed");
      }

      const data = await response.json();
      localStorage.setItem("token", data.token);
      
      setFormState(prev => ({
        ...prev,
        loading: false,
        success: true,
        error: null
      }));

      // Перенаправить на главную
      window.location.href = "/dashboard";
    } catch (error) {
      setFormState(prev => ({
        ...prev,
        loading: false,
        error: error.message
      }));
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {formState.error && (
        <div style={{ color: "red", marginBottom: "10px" }}>
          {formState.error}
        </div>
      )}

      <input
        type="email"
        value={formState.email}
        onChange={(e) => setFormState(prev => ({
          ...prev,
          email: e.target.value
        }))}
        placeholder="Email"
      />
      
      <input
        type="password"
        value={formState.password}
        onChange={(e) => setFormState(prev => ({
          ...prev,
          password: e.target.value
        }))}
        placeholder="Password"
      />
      
      <button 
        type="submit"
        disabled={formState.loading}
      >
        {formState.loading ? "Вход..." : "Войти"}
      </button>

      {formState.success && (
        <p style={{ color: "green" }}>Вход успешен!</p>
      )}
    </form>
  );
}

Лучшие практики

  1. Используй Error Boundaries для ловли render ошибок
  2. Используй try-catch в event handlers и async функциях
  3. Логируй ошибки на сервер для мониторинга
  4. Показывай friendly сообщения пользователю
  5. Предоставляй способ recover (retry, refresh)
  6. Обрабатывай loading и error состояния отдельно

Вывод

Полная стратегия обработки ошибок в React:

  • Error Boundaries — для ошибок в render
  • Try-catch — для async и event handlers
  • Error состояние — для UI feedback
  • Логирование — для мониторинга в production
  • Global handlers — для unhandled ошибок

Это обеспечивает надежное приложение, которое gracefully обрабатывает ошибки.

Как отловить и показать на экране ошибку в React? | PrepBro