Восстановление после ошибок

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


Элемент ErrorBoundary и маршруты

В React Router версии 6+ встроена поддержка компонента ErrorBoundary, который позволяет обрабатывать ошибки на уровне маршрутов. Он используется следующим образом:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Root from "./Root";
import Home from "./Home";
import NotFound from "./NotFound";
import ErrorPage from "./ErrorPage";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      { path: "/", element: <Home /> },
      { path: "*", element: <NotFound /> },
    ],
  },
]);

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

Ключевые моменты:

  • errorElement привязывается к конкретному маршруту и отображается при возникновении ошибки в дочерних компонентах.
  • Можно создавать вложенные ErrorBoundary для разных уровней маршрутов, что позволяет более гибко управлять ошибками.
  • *-маршрут используется для обработки “страниц не найдено”.

Доступ к информации об ошибке

Компонент, назначенный как errorElement, получает объект ошибки через хук useRouteError.

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

function ErrorPage() {
  const error = useRouteError();
  console.error(error);

  return (
    <div>
      <h1>Произошла ошибка</h1>
      <p>{error.statusText || error.message}</p>
    </div>
  );
}

Особенности:

  • useRouteError возвращает объект ошибки, который может содержать status, statusText, message и другие свойства, если ошибка связана с загрузкой данных.
  • Ошибки асинхронных загрузок маршрутов также перехватываются этим хук-компонентом.

Асинхронная загрузка данных и обработка ошибок

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

async function userLoader({ params }) {
  const response = await fetch(`/api/users/${params.userId}`);
  if (!response.ok) {
    throw new Response("Пользователь не найден", { status: 404 });
  }
  return response.json();
}

const router = createBrowserRouter([
  {
    path: "/users/:userId",
    element: <User />,
    loader: userLoader,
    errorElement: <UserErrorPage />,
  },
]);

Особенности работы:

  • Исключение, выброшенное в loader, передается в errorElement.
  • Можно использовать объекты Response для передачи статуса HTTP, что позволяет легко строить страницы ошибок типа 404 или 500.

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

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

const router = createBrowserRouter([
  {
    path: "/",
    element: <Root />,
    errorElement: <GlobalErrorPage />,
    children: [
      { path: "dashboard", element: <Dashboard /> },
      { path: "profile", element: <Profile />, errorElement: <ProfileError /> },
      { path: "*", element: <NotFound /> },
    ],
  },
]);

Выводы по структуре:

  • Локальные errorElement дают детальную обработку ошибок для конкретного маршрута.
  • Глобальный errorElement служит страховкой от непредвиденных исключений.
  • Вложенные обработчики ошибок не мешают друг другу и могут комбинироваться для комплексной логики.

Рекомендации по отображению ошибок

  1. Отображать информативные сообщения, включая статус ошибки и краткое описание.
  2. Сохранять пользовательский опыт, предоставляя кнопки для возврата на главный экран или предыдущую страницу.
  3. Логировать ошибки, чтобы отслеживать их причины и анализировать поведение приложения в продакшене.
  4. Использовать разные страницы ошибок для разных типов проблем: ошибки сети, ошибки данных, неизвестные маршруты.

Интеграция с Suspense и React.lazy

Асинхронные компоненты можно сочетать с обработкой ошибок, используя Suspense и lazy. Если при загрузке компонента произойдет исключение, оно будет поймано ближайшим errorElement.

import { lazy, Suspense } from "react";

const LazyComponent = lazy(() => import("./LazyComponent"));

<Route
  path="lazy"
  element={
    <Suspense fallback={<div>Загрузка...</div>}>
      <LazyComponent />
    </Suspense>
  }
  errorElement={<LazyErrorPage />}
/>

Примечания:

  • Suspense отвечает за отображение загрузочного состояния.
  • errorElement перехватывает исключения, возникшие во время динамического импорта или рендеринга.

Вывод

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