TanStack Query: Майстерність Керування Станом Сервера

React Suspense та Майбутнє

React 18 змінив правила гри. Suspense більше не експериментальна фіча. Це рекомендований спосіб роботи з асинхронністю.

React Suspense та Майбутнє

React 18 змінив правила гри. Suspense більше не експериментальна фіча. Це рекомендований спосіб роботи з асинхронністю.

TanStack Query v5 має першокласну підтримку Suspense.

useSuspenseQuery

Раніше ми писали так:

function Profile() {
  const { data, isLoading, isError } = useQuery(...);

  if (isLoading) return <Spinner />;
  if (isError) return <Error />;
  
  // Тут data може бути undefined, тому треба перевіряти
  return <div>{data?.name}</div>; 
}

З useSuspenseQuery ми пишемо так:

function Profile() {
  const { data } = useSuspenseQuery(...);
  
  // data ЗАВЖДИ визначена тут.
  // Якщо йде завантаження — компонент просто "зупиняється" (suspend).
  // React ловить це і показує найближчий Suspense fallback.
  return <div>{data.name}</div>;
}

Переваги

  1. Чистіший код: Ніяких if (isLoading).
  2. TypeScript: data типізована як TData, а не TData | undefined.
  3. Deklarative Loading: Ви керуєте спіннерами в батьківському компоненті, а не в кожному маленькому компоненті.

Структура Додатку

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    // 2. Якщо впаде помилка — покажеться це
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      
      {/* 1. Поки вантажиться — покажеться це */}
      <Suspense fallback={<div>Loading Profile...</div>}>
        <Profile />
        <Posts />
      </Suspense>
      
    </ErrorBoundary>
  );
}

Ви можете вкладати Suspense один в одного, створюючи гранулярний UI завантаження.

Transitions (Плавні переходи)

Суспенс має одну особливість: якщо ви змінюєте ключ запиту, старий UI зникає, і з'являється fallback (спіннер). Іноді ми хочемо залишити старий UI, поки вантажиться новий (як в Instagram при переході між табами).

Для цього використовується useTransition.

function TabNav() {
  const [tab, setTab] = useState('posts');
  const [isPending, startTransition] = useTransition();

  const handleClick = (newTab) => {
    // Кажемо React: "Це оновлення не термінове. 
    // Якщо воно спровокує Suspense, не ховай поточний UI, а просто почекай".
    startTransition(() => {
      setTab(newTab);
    });
  };

  return (
    <div>
      <div style={{ opacity: isPending ? 0.5 : 1 }}>
        <button onClick={() => handleClick('posts')}>Posts</button>
        <button onClick={() => handleClick('photos')}>Photos</button>
      </div>
      
      <Suspense fallback={<Spinner />}>
        {tab === 'posts' ? <Posts /> : <Photos />}
      </Suspense>
    </div>
  );
}

useSuspenseInfiniteQuery

Так само існує версія для нескінченного скролу.

Якщо ви використовуєте Suspense, ви зобов'язані мати Error Boundary. Інакше помилка "спливе" до самого верху і вб'є весь додаток (білий екран).

Коли НЕ використовувати Suspense?

Suspense — це блокуючий механізм. Поки дані не завантажаться, React не покаже нічого (крім фолбека). Якщо вам потрібен "background refetch" (показати старі дані, поки вантажаться нові), то звичайний useQuery з placeholderData може бути кращим варіантом.

Але для першого завантаження — Suspense перемагає.

Copyright © 2026