Задержки перед показом loader'ов

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


Зачем нужны задержки

Проблема появляется, когда асинхронная операция выполняется слишком быстро. Если loader показывается мгновенно, пользователь видит кратковременный всплеск индикатора, что создает ощущение “дерганного” интерфейса. Для решения этой проблемы применяются:

  • Минимальная задержка перед показом loader’а – позволяет интерфейсу оставаться стабильным, не показывая лишние анимации при быстрых ответах сервера.
  • Минимальное время отображения loader’а – гарантирует, что индикатор будет виден достаточно долго для восприятия, если загрузка была действительно продолжительной.

Конфигурация задержек в маршрутах

В TanStack Router для маршрута можно определить функцию loader и настроить поведение через дополнительные параметры. Основная логика заключается в применении обёртки с таймерами.

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

const userRoute = createRoute({
  path: '/user/:id',
  loader: async ({ params }) => {
    const minDelay = 300; // минимальная задержка перед показом loader'а в мс
    const startTime = Date.now();

    const data = await fetchUserData(params.id);

    const elapsed = Date.now() - startTime;
    if (elapsed < minDelay) {
      await new Promise(resolve => setTimeout(resolve, minDelay - elapsed));
    }

    return data;
  },
});

Пояснение ключевых моментов:

  • startTime фиксирует момент начала запроса.
  • Вычисление elapsed определяет фактическое время выполнения.
  • Если выполнение быстрее минимальной задержки (minDelay), используется setTimeout для искусственного удлинения времени загрузки.
  • В случае более долгой загрузки таймер не срабатывает, и loader отображается сразу.

Встроенные утилиты для управления loader’ом

TanStack Router поддерживает хуки и утилиты для глобального контроля состояния загрузки:

  1. useIsFetching() – возвращает булево значение, если в данный момент выполняются какие-либо загрузки.
  2. useTransition() – позволяет создавать кастомные переходы с задержкой отображения индикатора.
  3. Suspense – можно комбинировать с React Suspense для плавного появления loader’ов с минимальной задержкой.

Пример использования Suspense с задержкой:

import { Suspense } from 'react';

function DelayedLoader({ delay = 300 }) {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    const timer = setTimeout(() => setShow(true), delay);
    return () => clearTimeout(timer);
  }, [delay]);

  return show ? 
Загрузка...
: null; } function UserPage() { return ( }> ); }

Здесь DelayedLoader гарантирует, что индикатор не появится мгновенно, если данные подгружаются быстрее установленной задержки.


Рекомендации по применению

  • Для коротких запросов (до 200–300 мс) лучше использовать минимальную задержку перед показом loader’а, чтобы избежать мигания интерфейса.
  • Для длинных запросов (более 1 с) стоит комбинировать минимальное время отображения индикатора, чтобы пользователь видел стабильный прогресс.
  • Всегда тестировать на разных скоростях сети, так как быстрый локальный сервер и медленный внешний API ведут себя по-разному.

Совмещение с кешированием

TanStack Router интегрируется с кешем данных. Если данные уже загружены, loader может не отображаться вовсе. Для этого важно проверять состояние кеша перед вызовом задержки:

const loader = async ({ params, context }) => {
  const cached = context.cache.get(params.id);
  if (cached) return cached;

  const minDelay = 300;
  const start = Date.now();

  const data = await fetchUserData(params.id);

  const elapsed = Date.now() - start;
  if (elapsed < minDelay) {
    await new Promise(resolve => setTimeout(resolve, minDelay - elapsed));
  }

  context.cache.set(params.id, data);
  return data;
};

Такой подход уменьшает лишние показы loader’ов, ускоряет пользовательский опыт и делает интерфейс более плавным.