Skip to main content

Suspense і Data fetching

1. Що робить Suspense при завантаженні даних

Suspense дозволяє React призупиняти рендеринг частини дерева, поки компонент не отримає потрібні дані, і показувати fallback-UI (наприклад, спінер чи скелетон).

Тобто тепер React може "чекати" не тільки lazy()-компоненти, а й Promise із запиту даних.


2. Що змінилося в React 18

До React 18:

  • Suspense вмів чекати тільки ліниво завантажувані компоненти (React.lazy);
  • для даних доводилося вручну показувати Loading, перевіряти isLoading, обробляти помилки тощо.

З React 18+:

  • Suspense тепер може чекати Promise з будь-якого джерела (fetch, async-функції тощо);
  • React розуміє, що компонент "ще не готовий", і призупиняє його рендер, показуючи fallback;
  • коли Promise завершено -> React відновлює рендер з реальними даними.

3. Приклад "ручної" реалізації через Promise

Щоб зрозуміти механіку, подивімося на "низькорівневий" приклад без Next.js:

javascript
// utils/fetchData.js export function fetchData(url) { let status = 'pending'; let result; const suspender = fetch(url) .then(res => res.json()) .then(data => { status = 'success'; result = data; }) .catch(err => { status = 'error'; result = err; }); return { read() { if (status === 'pending') throw suspender; if (status === 'error') throw result; return result; }, }; }

Тепер використаємо його в компоненті:

javascript
const resource = fetchData('/api/user'); function Profile() { const user = resource.read(); // кидає Promise, якщо дані ще не готові return <h1>{user.name}</h1>; } function App() { return ( <Suspense fallback={<div>Завантаження профілю...</div>}> <Profile /> </Suspense> ); }

Що відбувається:

  1. resource.read() кидає Promise -> React "заморожує" рендер компонента Profile;
  2. React показує fallback із Suspense;
  3. Коли Promise завершується -> React "відновлює" рендер і вставляє Profile з даними.

4. Data Fetching у React Server Components (Next.js 13+)

У React 18+ і Next.js 13/14 Suspense інтегрований глибоко в серверний рендеринг.

Приклад:

javascript
// app/page.js (Next.js 13+) import { Suspense } from 'react'; import UserInfo from './UserInfo'; import UserPosts from './UserPosts'; export default async function Page() { const user = await getUser(); // async серверний компонент return ( <> <h1>{user.name}</h1> <Suspense fallback={<div>Завантаження постів...</div>}> {/* UserPosts завантажується незалежно */} <UserPosts userId={user.id} /> </Suspense> </> ); }

Тут React:

  • віддає першу частину HTML (ім'я користувача) одразу;
  • "призупиняє" рендеринг UserPosts, поки не отримає дані;
  • пізніше дострімлює HTML для UserPosts (Streaming SSR);
  • усе відбувається без перезавантаження сторінки.

Це працює завдяки новим API:

  • renderToPipeableStream() - Node.js streaming SSR;
  • renderToReadableStream() - для Deno, Edge тощо.

5. Комбінація Suspense + React Query / SWR

Поки React не має "вбудованого" fetch-механізму, але популярні бібліотеки вже додали підтримку Suspense:

javascript
import { useQuery } from '@tanstack/react-query'; import { Suspense } from 'react'; function Todos() { const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: () => fetch('/api/todos').then(res => res.json()), suspense: true, // ключова опція }); return <ul>{todos.map(t => <li key={t.id}>{t.title}</li>)}</ul>; } export default function App() { return ( <Suspense fallback={<div>Завантаження задач...</div>}> <Todos /> </Suspense> ); }

useQuery({ suspense: true }) повідомляє React, що під час завантаження потрібно кидати Promise, і Suspense автоматично відобразить fallback.


6. Як Suspense працює всередині

  1. Компонент викликає async-код (або хук), який викидає Promise;
  2. React "зустрічає" цей Promise;
  3. React:
  • відкладає рендер цієї гілки;
  • показує fallback з найближчого <Suspense>;
  1. Коли Promise завершується, React відновлює рендер;
  2. При SSR React може навіть "дострімити" готовий HTML на клієнт.

7. Що Suspense не робить сам

  • Не виконує fetch() напряму - це робить ваш код чи бібліотека.
  • Не обробляє помилки (catch) - для цього потрібен <ErrorBoundary>.
  • Не замінює useEffect - Suspense працює під час рендера, а не після.

Підсумок

Suspense в React 18 дозволяє React чекати дані так само, як він чекає компоненти. Він перетворює асинхронність на декларативну конструкцію: React "призупиняє" частину UI і "відновлює" її, коли все готово, без ручного isLoading, if (!data) тощо.


Коротко:

МожливістьДо React 18React 18+
Очікування async-компонентівНіТак
SSR + Streaming SuspenseНіТак
Інтеграція з React Query / SWRНіТак
Data Fetching в Server ComponentsНіТак
Fallback-UI без ручного isLoadingНіТак

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.