Skip to main content

Suspense and data fetching

1. The problem without Suspense

In a "classic" React application, data fetching looks like this:

javascript
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 / data states;
  • 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.Suspense lets you defer a component's render until its data is ready, and show a fallback instead of a manual loading state.

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:

  1. pauses rendering that part of the tree;
  2. shows the fallback from the nearest <Suspense>;
  3. once the promise resolves, rendering resumes automatically.

4. An example with SWR and Suspense

The SWR library works with Suspense out of the box.

javascript
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, useSWR throws a Promise until the data arrives;
  • React "catches" that Promise and 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:

javascript
<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.

javascript
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 SearchResults until 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).

javascript
// 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 Posts has loaded.

The UX is like an SPA, but with SSR: the page appears instantly, and data loads in pieces.


8. Comparing approaches

ApproachWhat it doesDownsides
useEffect + useStateManual loading managementCode duplication, UI "flashing"
SWR / React Query (without Suspense)Cache + state flagsA bit more complex when composing
SuspenseReact itself waits for the data and manages the UIRequires a compatible library (SWR, RQ, RSC)

SUMMARY

What Suspense does during data fetchingWhy it matters
Pauses the render until the data is readyAvoids an "empty" UI
Shows a fallback instead of a manual loaderSimplifies the code
Resumes the render automaticallyNo need for useEffect and setState
Works with several data sourcesEnables parallel loading
Integrates with concurrent renderingSmooth transitions without blocking
Used in Server ComponentsStreaming SSR

In short:

React.Suspense lets 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.