Suspense and data fetching
1. The problem without Suspense
In a "classic" React application, data fetching looks like this:
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>Loading...</p>;
return <h1>{user.name}</h1>;
}Drawbacks of this approach:
- you have to manually manage the
loading/error/datastates; - the code gets cluttered with this logic;
- the UI "flashes" on every transition (the loader is visible);
- React can't "pause" the render and show old data;
- there's no built-in way to synchronize several async components.
2. What Suspense does
React.Suspenselets you defer a component's render until its data is ready, and show a fallback instead of a manualloadingstate.
That is, React itself can "wait" for asynchronous data,
without you needing to manually write useEffect and useState.
3. How it works conceptually
When React encounters a component in the tree that isn't ready yet (for example, it throws a Promise),
React:
- pauses rendering that part of the tree;
- shows the
fallbackfrom the nearest<Suspense>; - once the promise resolves, rendering resumes automatically.
4. An example with SWR and Suspense
The SWR library works with Suspense out of the box.
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>Loading profile...</p>}>
<UserProfile />
</Suspense>
);
}What happens:
- on the first call,
useSWRthrows aPromiseuntil the data arrives; - React "catches" that
Promiseand stops the render; - it shows the
fallback; - once the request finishes, React automatically re-renders the component with the ready data.
No loading, useEffect, setState: React itself "waits".
5. Why this is great
Less manual code
No need to manually manage loading / error.
Composition of async components
Several components inside a Suspense can wait for their data at the same time:
<Suspense fallback={<DashboardSkeleton />}>
<UserProfile />
<UserStats />
<RecentActivities />
</Suspense>React waits until all three get their data, and shows them at once, without piecemeal flickering.
Smooth UX
The old content stays on screen, React shows the fallback only for the new data, instead of "clearing" the whole screen.
6. Suspense + startTransition = smooth updates
React 18 introduced concurrent rendering,
and now Suspense can be used for smooth data transitions.
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>
</>
);
}What React does:
- it doesn't block input (
startTransition); - under the hood, it pauses updating
SearchResultsuntil the data arrives; - it shows the fallback only for the "new state".
The result: a responsive interface, smooth search, no flickering or delays.
7. Suspense and React Server Components (Next.js 13+)
On the server (the App Router in Next.js),
Suspense is used for streaming rendering (Streaming SSR).
// app/page.tsx
export default async function Page() {
return (
<>
<Header />
<Suspense fallback={<PostsSkeleton />}>
<Posts /> {/* an async component */}
</Suspense>
</>
);
}Here React:
- renders the page on the server;
- immediately sends the user the ready parts (Header, layout);
- later "fills in" the rest once
Postshas loaded.
The UX is like an SPA, but with SSR: the page appears instantly, and data loads in pieces.
8. Comparing approaches
| Approach | What it does | Downsides |
|---|---|---|
| useEffect + useState | Manual loading management | Code duplication, UI "flashing" |
| SWR / React Query (without Suspense) | Cache + state flags | A bit more complex when composing |
| Suspense | React itself waits for the data and manages the UI | Requires a compatible library (SWR, RQ, RSC) |
SUMMARY
| What Suspense does during data fetching | Why it matters |
|---|---|
| Pauses the render until the data is ready | Avoids an "empty" UI |
| Shows a fallback instead of a manual loader | Simplifies the code |
| Resumes the render automatically | No need for useEffect and setState |
| Works with several data sources | Enables parallel loading |
| Integrates with concurrent rendering | Smooth transitions without blocking |
| Used in Server Components | Streaming SSR |
In short:
React.Suspenselets React "wait" for data asynchronously during data fetching, showing a fallback, and then automatically continue the render once the data arrives.This makes the code simpler, the interface smoother, and the UX instant and predictable.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.