Suspense and Data Fetching
1. What Suspense does while data loads
Suspenselets React pause rendering part of the tree until a component receives the data it needs, and show fallback UI (for example, a spinner or a skeleton).
That is, React can now "wait" not only for lazy() components,
but also for a Promise from a data request.
2. What changed in React 18
Before React 18:
Suspensecould only wait for lazily loaded components (React.lazy);- for data you had to manually show
Loading, checkisLoading, handle errors, and so on.
With React 18+:
Suspensecan now wait for a Promise from any source (fetch, async functions, and so on);- React understands that a component is "not ready yet" and pauses its render,
showing the
fallback; - once the Promise resolves -> React resumes the render with the real data.
3. An example of a "manual" implementation with a Promise
To understand the mechanics, let's look at a "low-level" example without Next.js:
// 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;
},
};
}Now let's use it in a component:
const resource = fetchData('/api/user');
function Profile() {
const user = resource.read(); // throws a Promise if the data is not ready yet
return <h1>{user.name}</h1>;
}
function App() {
return (
<Suspense fallback={<div>Loading profile...</div>}>
<Profile />
</Suspense>
);
}What happens:
resource.read()throws a Promise -> React "freezes" the render of theProfilecomponent;- React shows the
fallbackfromSuspense; - When the Promise resolves -> React "resumes" the render and inserts
Profilewith the data.
4. Data Fetching in React Server Components (Next.js 13+)
In React 18+ and Next.js 13/14, Suspense is deeply integrated into server rendering.
Example:
// 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(); // an async server component
return (
<>
<h1>{user.name}</h1>
<Suspense fallback={<div>Loading posts...</div>}>
{/* UserPosts loads independently */}
<UserPosts userId={user.id} />
</Suspense>
</>
);
}Here React:
- sends the first part of the HTML (the user's name) right away;
- "pauses" the rendering of
UserPostsuntil it gets the data; - later streams in the HTML for
UserPosts(Streaming SSR); - all of this happens without reloading the page.
This works thanks to new APIs:
renderToPipeableStream()- Node.js streaming SSR;renderToReadableStream()- for Deno, Edge, and so on.
5. Combining Suspense with React Query / SWR
React does not yet have a "built-in" fetch mechanism, but popular libraries have already added Suspense support:
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, // the key option
});
return <ul>{todos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
}
export default function App() {
return (
<Suspense fallback={<div>Loading tasks...</div>}>
<Todos />
</Suspense>
);
}useQuery({ suspense: true }) tells React that it needs to throw a Promise while loading,
and Suspense automatically displays the fallback.
6. How Suspense works internally
- A component calls async code (or a hook) that throws a
Promise; - React "encounters" that
Promise; - React:
- defers the render of that branch;
- shows the
fallbackfrom the nearest<Suspense>;
- When the Promise resolves, React resumes the render;
- With SSR, React can even stream in the finished HTML to the client.
7. What Suspense does not do on its own
- It does not perform
fetch()directly - your code or a library does that. - It does not handle errors (
catch) - for that you need an<ErrorBoundary>. - It does not replace
useEffect- Suspense works during rendering, not after.
Summary
Suspense in React 18 lets React wait for data the same way it waits for components. It turns asynchrony into a declarative construct: React "pauses" part of the UI and "resumes" it once everything is ready, without manual
isLoading,if (!data), and so on.
In short:
| Capability | Before React 18 | React 18+ |
|---|---|---|
| Waiting for async components | No | Yes |
| SSR + Streaming Suspense | No | Yes |
| Integration with React Query / SWR | No | Yes |
| Data Fetching in Server Components | No | Yes |
Fallback UI without manual isLoading | No | Yes |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.