Lesson 36 of 80
use() reads a Promise or a Context value — and unlike every hook before it, you're allowed to call it inside a condition or a loop.
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
use() looks like a hook, is imported like a hook, and sits in the same family as useState and useEffect — but React's own docs are explicit that it isn't a traditional hook. It reads one of two things: a Promise (and suspends the component until it resolves) or a Context (same job as useContext). What makes it different is where you're allowed to call it: inside an if, after an early return, inside a loop. Every other hook forbids that. use() doesn't, because it isn't bound by the same rule that makes conditional hook calls dangerous — it isn't tracking per-render call order in a fixed list the way useState does.
The one rule that doesn't relax: use() still only works during render. You can't stash it in an event handler or call it from inside a setTimeout.
The pattern use() was built for: a Server Component kicks off a fetch but doesn't await it, hands the raw Promise down as a prop, and a Client Component further down the tree reads it — letting the shell of the page render immediately while the slow part streams in behind a Suspense boundary.
// Server Component — starts the fetch, does NOT await it
function ProfilePage({ userId }) {
const commentsPromise = fetchComments(userId); // fire-and-hand-off, not awaited
return (
<Suspense fallback={<CommentsSkeleton />}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// Client Component — reads the promise
"use client";
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // suspends here until it resolves
return <ul>{comments.map((c) => <li key={c.id}>{c.text}</li>)}</ul>;
}
ProfilePage returns instantly — it never waits on the comments. Comments is the one that pauses, and only Comments, which is exactly the granularity Suspense is good at: the rest of the page doesn't have to wait on the slowest child.
The Context case is smaller but shows off the conditional-call ability directly:
function Message({ show }) {
if (!show) return null; // early return — illegal before a useContext() call
const theme = use(ThemeContext); // legal here, specifically because it's use()
return <p style={{ color: theme.text }}>Hi</p>;
}
With useContext, that early return before the hook call would break the rules of hooks outright. With use(), it's fine — React re-evaluates the call fresh each time it's actually reached, rather than depending on a stable call order.
use() breaking the rules of hooks is deliberate, not a loophole. It's the one hook-like API allowed to be conditional, and that's a documented, specific exception — it doesn't mean conditional useState or useEffect calls are suddenly safe too.use(), the failure bubbles further up than you'd expect.use() isn't a general-purpose data-fetching hook. It's a primitive that other tools (or your own code) build on top of; calling fetch() directly inside a component and immediately use()-ing the result, on every render, is the exact anti-pattern the callout above warns about.The mechanism that catches a use() call on a pending promise is the identical suspend-and-show-a-fallback machinery that first showed up for lazily-loaded component code — this just generalizes it so any async value, not only a lazily-imported component, can pause a render until it's ready.