Pending-компоненты

Pending-компоненты в TanStack Router обеспечивают возможность отображения промежуточного состояния интерфейса, когда данные ещё загружаются или выполняется асинхронная операция перед рендером основной страницы. В современном веб-программировании это ключевой инструмент для создания отзывчивых и плавных интерфейсов без “мерцания” и резкой смены контента.

Асинхронные данные и маршруты

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

import { createRouter, Route } from '@tanstack/router';

const userRoute = new Route({
  path: '/user/:id',
  loader: async ({ params }) => {
    const response = await fetch(`/api/user/${params.id}`);
    return response.json();
  },
  component: UserPage,
});

Если loader возвращает промис, рендеринг компонента будет отложен до завершения промиса. Здесь и вступает в работу Pending-компонент, который позволяет показывать пользовательский интерфейс во время ожидания данных.

Создание Pending-компонента

Pending-компонент — это отдельный React-компонент, который рендерится автоматически в момент ожидания загрузки данных. Его подключение осуществляется через свойство pendingComponent маршрута:

const userRoute = new Route({
  path: '/user/:id',
  loader: async ({ params }) => {
    const response = await fetch(`/api/user/${params.id}`);
    return response.json();
  },
  component: UserPage,
  pendingComponent: () => <div>Загрузка данных пользователя...</div>,
});

Важное свойство: Pending-компонент не получает пропсы от маршрута напрямую. Его задача — отображение состояния ожидания, а не работа с данными.

Контроль времени отображения

TanStack Router позволяет гибко управлять тем, как долго Pending-компонент показывается. Для этого используется опция pendingMs, задающая минимальное время отображения:

const userRoute = new Route({
  path: '/user/:id',
  loader: async ({ params }) => {
    const response = await fetch(`/api/user/${params.id}`);
    return response.json();
  },
  component: UserPage,
  pendingComponent: () => <div>Загрузка...</div>,
  pendingMs: 300, // Показывать не меньше 300 мс
});

Это предотвращает “мерцание” интерфейса, если данные загружаются слишком быстро.

Вложенные маршруты и Pending-компоненты

Pending-компоненты работают и с вложенными маршрутами. Вложенный маршрут может иметь собственный Pending-компонент, который будет отображаться отдельно от родительского:

const userPostsRoute = new Route({
  path: 'posts',
  loader: async ({ params }) => {
    const response = await fetch(`/api/user/${params.id}/posts`);
    return response.json();
  },
  component: UserPostsPage,
  pendingComponent: () => <div>Загрузка постов...</div>,
});

const userRoute = new Route({
  path: '/user/:id',
  component: UserPage,
  children: [userPostsRoute],
  pendingComponent: () => <div>Загрузка информации пользователя...</div>,
});

В этом примере пока загружается основной маршрут /user/:id, отображается один Pending-компонент, а при переходе к /user/:id/posts показывается отдельный Pending-компонент для постов.

Состояние ожидания в React

TanStack Router предоставляет хук usePending, который позволяет отслеживать состояние загрузки маршрута и создавать кастомные Pending-компоненты:

import { usePending } from '@tanstack/router';

function UserPage() {
  const pending = usePending();
  
  return (
    <div>
      {pending ? <div>Данные загружаются...</div> : <UserData />}
    </div>
  );
}

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

Совмещение с Suspense

Pending-компоненты TanStack Router легко интегрируются с React Suspense. Маршрут с Suspense позволяет рендерить асинхронные компоненты, используя fallback, что делает Pending-компоненты и Suspense полностью совместимыми:

import React, { Suspense } from 'react';

const UserData = React.lazy(() => import('./UserData'));

const userRoute = new Route({
  path: '/user/:id',
  component: () => (
    <Suspense fallback={<div>Загрузка данных пользователя...</div>}>
      <UserData />
    </Suspense>
  ),
});

Это обеспечивает плавную загрузку даже для тяжёлых компонентов и поддерживает единообразный UX.

Рекомендации по использованию

  • Минимизировать текстовые блоки в Pending-компонентах, использовать скелетоны или индикаторы прогресса для лучшего визуального отклика.
  • Определять минимальное время отображения (pendingMs), чтобы избежать мерцания интерфейса.
  • Использовать отдельные Pending-компоненты для вложенных маршрутов, чтобы пользователи видели локальную загрузку данных, а не общий спиннер.
  • Комбинировать с Suspense, если используются асинхронные React-компоненты, для унифицированного управления состоянием ожидания.

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