Lesson 62 of 80
Suspense for code-splitting waits on a component's code. Suspense for data fetching waits on the data that component needs — and that difference changes what can go wrong.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The suspense-and-lazy-loading lesson covers React.lazy + Suspense: a component's code hasn't arrived yet, so rendering pauses until the dynamic import() resolves. That's one specific thing that can make a component "not ready yet." Data is another — a component might have all its code but still be waiting on a network request for the data it needs to render anything meaningful.
Suspense doesn't care which kind of "not ready" it's dealing with. The mechanism is the same underlying protocol both times: something throws a Promise, the nearest <Suspense> boundary catches it, shows the fallback, and re-renders the subtree once the Promise resolves. React.lazy throws a promise for code; use() throws a promise for data. Same boundary, same fallback, different thing being waited on.
use() suspending on a promisefunction UserProfile({ userPromise }) {
const user = use(userPromise); // suspends here until userPromise settles
return <h1>{user.name}</h1>;
}
function ProfilePage({ userId }) {
const userPromise = useMemo(() => fetchUser(userId), [userId]); // created once per userId, not per render
return (
<Suspense fallback={<Spinner />}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
use(userPromise) reads the promise: if it's still pending, use throws it, Suspense catches the throw and renders <Spinner />. Once the promise resolves, React re-renders UserProfile, use() returns the resolved value directly (no .then, no separate loading state variable), and the real content shows.
use() on a data promise — its own Suspense boundary (or an outer one) covers both waits, code and data, without the component needing to know which kind of wait is happening.use() isn't limited to data fetching promises specifically — it can read a Context value too (its other use case), and in both cases it's the first hook that's allowed to be called conditionally, since it's designed to work inside early returns and loops.react-cache-style utilities, query libraries with suspense mode) all supply a caching layer so the same logical request returns the same promise instance across renders — hand-rolling this without a cache is exactly what leads to the unstable-promise problem above.Both this lesson and the suspense-and-lazy-loading lesson are the identical <Suspense> mechanism — a boundary catching a thrown promise and showing a fallback until it resolves. The difference is entirely in what's being awaited: React.lazy throws a promise for a code chunk that only ever needs to resolve once per module; use() throws a promise for data that can change per props, per user, per render — which is exactly why keeping that promise stable across renders becomes the whole game here in a way it never has to be for code-splitting.