Обработка ошибок загрузки данных

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


Loader-функции и их роль

Каждый маршрут может иметь функцию loader, которая вызывается перед рендерингом компонента и отвечает за получение необходимых данных. Loader возвращает промис, и если он завершается успешно, данные передаются компоненту через хук useLoaderData().

import { createBrowserRouter, RouterProvider, useLoaderData } from "react-router-dom";

async function userLoader({ params }) {
  const response = await fetch(`/api/users/${params.userId}`);
  if (!response.ok) {
    throw new Response("Ошибка загрузки данных пользователя", { status: response.status });
  }
  return response.json();
}

function User() {
  const user = useLoaderData();
  return <div>{user.name}</div>;
}

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

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

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

  • loader вызывается автоматически при переходе на маршрут.
  • Ошибки внутри loader можно перехватывать через throw new Response(...) или обычный throw new Error(...).
  • Данные, возвращаемые loader, доступны только через useLoaderData.

Использование errorElement

Для отображения ошибок на уровне маршрута используется errorElement. Это позволяет показывать пользователю понятное сообщение без необходимости оборачивать каждый компонент в try/catch.

function UserError({ error }) {
  return (
    <div>
      <h2>Не удалось загрузить пользователя</h2>
      <p>{error.statusText || error.message}</p>
    </div>
  );
}

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

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

  • Если loader выбрасывает ошибку, React Router автоматически рендерит errorElement.
  • Ошибка передается в компонент через проп error, который может содержать объект Response или стандартную ошибку JavaScript.
  • Можно использовать разные errorElement для разных маршрутов, что обеспечивает гибкость и локализацию обработки ошибок.

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

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

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

const router = createBrowserRouter([
  {
    path: "/",
    element: <AppLayout />,
    errorElement: <RootError />,
    children: [
      { path: "users/:userId", element: <User />, loader: userLoader },
      { path: "posts/:postId", element: <Post />, loader: postLoader },
    ],
  },
]);

Преимущества:

  • Нет необходимости дублировать обработку ошибок в каждом маршруте.
  • Все неожиданные ошибки, включая сетевые и программные, перехватываются централизованно.
  • Позволяет выводить единый UI для всех ошибок, что улучшает пользовательский опыт.

Работа с асинхронными ошибками и Suspense

React Router поддерживает асинхронные маршруты с помощью defer и Suspense. Это особенно важно при загрузке больших объемов данных. Ошибки внутри defer также могут быть перехвачены через errorElement.

import { defer, Await } from "react-router-dom";

function userLoader({ params }) {
  return defer({
    user: fetch(`/api/users/${params.userId}`).then(res => {
      if (!res.ok) throw new Error("Ошибка загрузки");
      return res.json();
    }),
  });
}

function User() {
  const { user } = useLoaderData();
  return (
    <Suspense fallback={<p>Загрузка...</p>}>
      <Await resolve={user}>
        {(data) => <div>{data.name}</div>}
      </Await>
    </Suspense>
  );
}

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

  • defer позволяет рендерить компонент до завершения полной загрузки данных.
  • Ошибки, возникшие внутри Await, будут направлены в ближайший errorElement.
  • Использование Suspense с Await обеспечивает плавный пользовательский интерфейс без зависаний.

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

  1. Использовать throw new Response(...) для корректного задания статуса HTTP и сообщения.
  2. Разделять сетевые ошибки и ошибки данных для точной диагностики.
  3. Локальные и глобальные errorElement комбинировать для разных уровней маршрутов.
  4. Логировать ошибки в консоль или на сервер для последующего анализа.
  5. Тестировать сценарии с недоступным API для проверки поведения errorElement.

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