Suspense і завантаження даних
1. Проблема без Suspense
У "класичному" React-застосунку завантаження даних виглядає так:
function Profile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setUser)
.finally(() => setLoading(false));
}, []);
if (loading) return <p>Завантаження...</p>;
return <h1>{user.name}</h1>;
}Недоліки такого підходу:
- доводиться вручну керувати станами
loading/error/data; - код сильно захаращується логікою;
- UI "мигає" при кожному переході (видно loader);
- React не може "призупинити" рендер і показати старі дані;
- немає вбудованого способу синхронізувати кілька асинхронних компонентів.
2. Що робить Suspense
React.Suspenseдозволяє відкласти рендер компонента, поки не будуть готові його дані, і показати fallback замість ручного стануloading.
Тобто React може "почекати" асинхронні дані сам,
без потреби вручну писати useEffect і useState.
3. Як це працює концептуально
Коли React всередині дерева зустрічає компонент, який ще не готовий (наприклад, він кидає Promise),
React:
- призупиняє рендер цієї ділянки дерева;
- показує
fallbackз найближчого<Suspense>; - коли проміс завершується - рендер відновлюється автоматично.
4. Приклад з SWR і Suspense
Бібліотека SWR вміє працювати з Suspense "з коробки".
import useSWR from "swr";
import { Suspense } from "react";
const fetcher = (url) => fetch(url).then(res => res.json());
function UserProfile() {
const { data } = useSWR("/api/user", fetcher, { suspense: true });
return <h1>Hello, {data.name}</h1>;
}
export default function App() {
return (
<Suspense fallback={<p>Завантаження профілю...</p>}>
<UserProfile />
</Suspense>
);
}Що відбувається:
useSWRпри першому виклику кидаєPromise, поки дані не прийшли;- React "ловить" цей
Promise, зупиняє рендер; - показує
fallback; - коли запит завершено -> React автоматично рендерить компонент заново з готовими даними.
Без loading, useEffect, setState - React сам "чекає".
5. Чому це круто
Менше ручного коду
Не потрібно вручну керувати loading / error.
Композиція асинхронних компонентів
Кілька компонентів всередині Suspense можуть одночасно чекати свої дані:
<Suspense fallback={<DashboardSkeleton />}>
<UserProfile />
<UserStats />
<RecentActivities />
</Suspense>React почекає, поки всі три отримають дані, і покаже їх одночасно -> без "мерехтіння" частинами.
Плавний UX
Старий контент залишається на екрані, React показує fallback тільки для нових даних, а не "очищає" весь екран.
6. Suspense + startTransition = плавні оновлення
React 18 представив concurrent rendering,
і тепер Suspense можна використовувати для плавних переходів даних.
import { useState, Suspense, startTransition } from "react";
import useSWR from "swr";
function SearchResults({ query }) {
const { data } = useSWR(`/api/search?q=${query}`, fetcher, { suspense: true });
return <ul>{data.results.map(r => <li key={r}>{r}</li>)}</ul>;
}
export function Search() {
const [query, setQuery] = useState('');
return (
<>
<input
value={query}
onChange={e => {
const value = e.target.value;
startTransition(() => setQuery(value));
}}
placeholder="Search..."
/>
<Suspense fallback={<p>Loading...</p>}>
<SearchResults query={query} />
</Suspense>
</>
);
}Що робить React:
- не блокує ввід (
startTransition); - під капотом призупиняє оновлення
SearchResults, поки дані не прийдуть; - показує fallback тільки для "нового стану".
У результаті: інтерфейс чуйний, пошук плавний, без мигань і затримок.
7. Suspense і React Server Components (Next.js 13+)
На сервері (App Router у Next.js)
Suspense використовується для потокового рендера (Streaming SSR).
// app/page.tsx
export default async function Page() {
return (
<>
<Header />
<Suspense fallback={<PostsSkeleton />}>
<Posts /> {/* async-компонент */}
</Suspense>
</>
);
}Тут React:
- рендерить сторінку на сервері;
- одразу надсилає користувачу готові частини (Header, layout);
- пізніше "добудовує" решту, коли
Postsзавантажиться.
UX - як у SPA, але з SSR: сторінка з'являється миттєво, дані підвантажуються частинами.
8. Порівняння підходів
| Підхід | Що робить | Мінуси |
|---|---|---|
| useEffect + useState | Ручне керування завантаженням | Дублювання коду, "мигання" UI |
| SWR / React Query (без Suspense) | Кеш + прапорці стану | Трохи складніше при композиції |
| Suspense | React сам чекає дані і керує UI | Потребує сумісної бібліотеки (SWR, RQ, RSC) |
ПІДСУМОК
| Що робить Suspense при завантаженні даних | Чому це важливо |
|---|---|
| Призупиняє рендер, поки дані не готові | Уникає "порожнього" UI |
| Показує fallback замість ручного loader'а | Спрощує код |
| Відновлює рендер автоматично | Не потрібні useEffect і setState |
| Працює з кількома джерелами даних | Дозволяє паралельне завантаження |
| Інтегрується з concurrent rendering | Плавні переходи без блокування |
| Використовується в Server Components | Потоковий SSR (Streaming) |
Коротко:
React.Suspenseпри завантаженні даних дозволяє React "чекати" дані асинхронно, показуючи fallback, а потім автоматично продовжувати рендер, коли дані прийшли.Це робить код простішим, інтерфейс плавнішим, а UX - миттєвим і передбачуваним.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.