ErrorBoundary в маршрутах

React Router предоставляет мощный механизм для управления навигацией в приложении, но при работе с маршрутизацией могут возникать ошибки, связанные как с самим маршрутом, так и с компонентами, которые он рендерит. Для безопасного и предсказуемого поведения приложения используется концепция Error Boundary.


Основы ErrorBoundary

Error Boundary — это React-компонент, который перехватывает ошибки во время рендеринга, в методах жизненного цикла и в конструкторах дочерних компонентов. Он не перехватывает ошибки в асинхронных колбэках, событиях или серверном рендеринге.

Ключевые методы для реализации:

  • static getDerivedStateFromError(error) — позволяет обновить состояние компонента при возникновении ошибки.
  • componentDidCatch(error, info) — используется для логирования ошибки или отправки её на сервер.

Пример базового ErrorBoundary:

import React from 'react';

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

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, info) {
    console.error('Ошибка в компоненте:', error);
    console.error('Информация о стеке:', info);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Произошла ошибка: {this.state.error.message}</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

Интеграция ErrorBoundary с React Router

В React Router (версии 6 и выше) появилась возможность интегрировать ErrorBoundary непосредственно в маршруты через свойство errorElement. Это позволяет каждому маршруту обрабатывать собственные ошибки независимо, без необходимости оборачивать весь роутер одним глобальным ErrorBoundary.

Пример использования:

import { createBrowserRouter, RouterProvider, Route } from 'react-router-dom';
import ErrorBoundary from './ErrorBoundary';
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ProfilePage from './ProfilePage';

const router = createBrowserRouter([
  {
    path: '/',
    element: <HomePage />,
    errorElement: <ErrorBoundary />,
  },
  {
    path: '/about',
    element: <AboutPage />,
    errorElement: <ErrorBoundary />,
  },
  {
    path: '/profile/:id',
    element: <ProfilePage />,
    errorElement: <ErrorBoundary />,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

В этом примере каждая страница имеет собственный ErrorBoundary, что обеспечивает локализованное управление ошибками.


Асинхронные ошибки в маршрутах

React Router активно использует loader, action и defer, которые могут возвращать ошибки асинхронно. Чтобы обработать такие ошибки, можно использовать useRouteError внутри компонента ErrorBoundary, указанного в errorElement.

Пример:

import { useRouteError } from 'react-router-dom';

function RouteError() {
  const error = useRouteError();
  console.error('Ошибка маршрута:', error);
  
  return (
    <div>
      <h2>Не удалось загрузить страницу</h2>
      <p>{error.statusText || error.message}</p>
    </div>
  );
}

Примеры сценариев ошибок

  1. Ошибка в компоненте: если HomePage выбрасывает исключение во время рендеринга, ErrorBoundary перехватит её и отобразит пользовательское сообщение.

  2. Ошибка загрузки данных (loader): при сбое в асинхронной загрузке данных для маршрута /profile/:id компонент RouteError получит объект ошибки с status и statusText.

  3. Ошибка действия (action): при отправке формы на маршруте /about и выбросе ошибки внутри action, соответствующий errorElement отобразит сообщение об ошибке.


Локальные и глобальные ErrorBoundary

  • Локальные: размещаются непосредственно на уровне маршрута через errorElement. Подход полезен, когда разные страницы требуют разной обработки ошибок.
  • Глобальные: оборачивают <RouterProvider> или <Routes> и обеспечивают единую обработку всех ошибок.

Пример глобального ErrorBoundary:

function App() {
  return (
    <ErrorBoundary>
      <RouterProvider router={router} />
    </ErrorBoundary>
  );
}

Комбинация локальных и глобальных ErrorBoundary позволяет контролировать и ошибки маршрутов, и ошибки на уровне всего приложения.


Важные рекомендации

  • Всегда использовать errorElement для маршрутов, которые загружают данные асинхронно.
  • Локализовать обработку ошибок для отдельных маршрутов, чтобы предотвращать падение всего приложения.
  • Логировать ошибки для последующего анализа и мониторинга.
  • Не использовать ErrorBoundary для управления состоянием загрузки — для этого существуют Suspense и состояния загрузки внутри компонентов.

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