Концепция loader в React Router

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


Определение loader

loader определяется на уровне маршрута и вызывается автоматически при переходе на соответствующий путь. Он должен возвращать данные, которые затем доступны через хук useLoaderData внутри компонента маршрута.

Пример базовой структуры маршрута с loader:

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App";
import UserPage from "./UserPage";

const router = createBrowserRouter([
  {
    path: "/users/:userId",
    element: <UserPage />,
    loader: async ({ params }) => {
      const response = await fetch(`https://api.example.com/users/${params.userId}`);
      if (!response.ok) throw new Response("User not found", { status: 404 });
      return response.json();
    },
  },
]);

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

В данном примере loader получает объект params, содержащий параметры маршрута (userId), и возвращает результат запроса к API.


Аргументы функции loader

Функция loader получает один объект с несколькими ключами:

  • params – объект параметров маршрута (:userId, :postId и т.д.)
  • request – объект Request, аналогичный стандартному Fetch API, предоставляющий информацию о текущем запросе
  • context – опциональный объект, передаваемый через маршрутизатор для передачи глобальных данных

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

loader: async ({ params, request, context }) => {
  const token = context.authToken;
  const response = await fetch(`https://api.example.com/users/${params.userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  return response.json();
}

Получение данных в компоненте

После того как loader возвращает данные, их можно получить с помощью хука useLoaderData:

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

function UserPage() {
  const user = useLoaderData();
  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

useLoaderData гарантирует, что компонент получает уже загруженные данные, исключая необходимость дополнительного состояния и useEffect для запроса данных.


Обработка ошибок в loader

Если запрос в loader завершается ошибкой или возвращается Response с кодом ошибки, React Router позволяет обрабатывать такие случаи через компонент ErrorBoundary или errorElement маршрута.

Пример:

{
  path: "/users/:userId",
  element: <UserPage />,
  loader: async ({ params }) => {
    const response = await fetch(`https://api.example.com/users/${params.userId}`);
    if (!response.ok) throw new Response("User not found", { status: 404 });
    return response.json();
  },
  errorElement: <UserErrorPage />,
}

В компоненте UserErrorPage можно получить объект ошибки через хук useRouteError:

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

function UserErrorPage() {
  const error = useRouteError();
  return <div>Ошибка: {error.statusText || "Неизвестная ошибка"}</div>;
}

Параллельная и последовательная загрузка данных

При сложных маршрутах с вложенными маршрутами loader каждого уровня вызывается независимо, но React Router позволяет загружать данные последовательно или параллельно:

  • Последовательно: родительский loader выполняется первым, затем дочерний. Полезно, когда дочерний зависит от данных родителя.
  • Параллельно: все loader вызываются одновременно, ускоряя загрузку, если зависимости отсутствуют.

Использование loader для редиректов

loader может инициировать перенаправление с помощью утилиты redirect из react-router-dom:

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

loader: async ({ params }) => {
  const isAuthorized = await checkAuth();
  if (!isAuthorized) return redirect("/login");
  return fetchUserData(params.userId);
}

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


Интеграция с Suspense и асинхронными компонентами

React Router 6.10+ поддерживает использование loader вместе с Suspense. Если loader возвращает Promise, маршрутизатор может отобразить fallback UI до завершения загрузки данных:

const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: (
      <React.Suspense fallback={<div>Загрузка...</div>}>
        <Dashboard />
      </React.Suspense>
    ),
    loader: async () => fetch("/api/dashboard").then(res => res.json()),
  },
]);

Это повышает отзывчивость интерфейса и обеспечивает плавное отображение данных.


Ключевые преимущества loader

  • Синхронизация данных и маршрутов: данные загружаются до рендеринга компонента.
  • Упрощение компонентов: компоненты получают готовые данные через useLoaderData.
  • Управление ошибками и редиректами на уровне маршрута.
  • Совместимость с Suspense и современными асинхронными подходами React.

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