In React 19, a client component runs `const data = use(dataPromise)` where `dataPromise` arrived as a prop. What does that call evaluate to, and what does React do with the component while the promise is still pending?
answer
- reads a resource during render
- the component keeps no loading flag
- pending means the component suspends
- nearest Suspense fallback shows instead
- returns the fulfilled value, not a promise
basics
~10 suse(dataPromise) returns the value the promise fulfilled with. While the promise is pending, React suspends the component and shows the nearest Suspense boundary's fallback, so the component itself holds no loading state.
solid answer
~40 s`use` is a React 19 API that reads a resource during render. Calling `use(dataPromise)` has three outcomes: if the promise is pending, React suspends — it abandons rendering that component for now and the closest `<Suspense>` ancestor shows its fallback; when the promise fulfills, React re-renders the component and `use` returns the fulfilled value synchronously, as a plain value, not a promise; if it rejects, the error is thrown from render and the nearest error boundary takes over. The component stays an ordinary synchronous function — no `async`, no `await`, no `isLoading` or `error` state, and no early `if (loading) return <Spinner />`. The loading UI is declared by the parent boundary instead of by the component that needs the data.
code
jsx · 16 linesimport { Suspense, use } from 'react';
const userPromise = fetch('/api/user/1').then((r) => r.json());
function Profile({ userPromise }) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
export default function Page() {
return (
<Suspense fallback={<p>Loading…</p>}>
<Profile userPromise={userPromise} />
</Suspense>
);
}go deeper
Be able to say plainly that use(promise) gives you the resolved value and that while the promise is pending React shows the nearest Suspense fallback instead of the component. Mention that no isLoading state is needed.
Explain the mechanics: React abandons the render, re-runs the component from the top after the promise settles, and routes rejection to an error boundary — so the component only ever describes the success case.
Show judgment about where the promise comes from: started outside render or handed down as a prop, never created fresh in a client render body, and be ready to say which ancestor should own the loading UI.
Frame it as moving loading and error handling out of leaf components and into boundary placement — a system-wide decision about perceived performance and where failure is contained, not a local coding style choice.
## What the call looks like ```jsx import { use, Suspense } from 'react'; function Profile({ userPromise }) { const user = use(userPromise); // a plain object, not a promise return <h1>{user.name}</h1>; } function Page({ userPromise }) { return ( <Suspense fallback={<p>Loading…</p>}> <Profile userPromise={userPromise} /> </Suspense> ); } ``` `Profile` is a normal synchronous function component. It is not `async`, and `use` is not `await` — it does not pause a JavaScript function, it participates in React's render machinery. ## The three outcomes of use(promise) **Pending.** React cannot produce output for this component yet, so it *suspends* it: rendering of that subtree stops and React looks upward for the closest `<Suspense>` ancestor, whose `fallback` is displayed in its place. Nothing about the component is "paused" and resumed mid-function; the function call is simply abandoned and will be run again from the top later. **Fulfilled.** When the promise settles successfully, React re-renders the component. This time the render body runs to completion and `use(userPromise)` returns the fulfilled value directly — the object, string, or array the promise resolved with. There is no intermediate render where the value is `undefined`; either the component renders with real data or it does not render at all. **Rejected.** The rejection reason is thrown out of the component during render, which is exactly the signal an error boundary is built to catch. So the failure path also leaves the component: no `catch`, no `error` state variable. ## What disappears from the component The pre-`use` shape of a data-reading client component was a small state machine written by hand: ```jsx const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); ``` plus an effect to kick off the request and three early returns to render the three states. With `use`, all three states are still present in the UI, but React owns two of them: pending is expressed by the Suspense fallback and failure by the error boundary. The component body only ever describes the success case, which is why the code shrinks so dramatically. A useful way to say it in an interview: the component stops *reporting* its loading state and starts *being* loading. The decision about what the user sees while data is missing moves from the leaf component to whichever ancestor drew the boundary — a placement decision, not a coding decision. ## Why the promise must come from outside the render `use` reads a promise; it does not create or own one. If a client component creates the promise in its own render body, React has no cache for it, so the next render produces a brand-new pending promise and the component suspends forever. In practice the promise is either started outside the component (module scope, an event, a cache keyed by id) or handed down as a prop from code that ran earlier — commonly a Server Component that began the request and passed the un-awaited promise across. React streams the eventual value to the client, and `use` is what unwraps it there. ## use() versus await In an async Server Component you can simply `await` a promise, because that component runs once on the server and its output is serialized. `use` is what makes the same idea available inside a normal client component render, which cannot be `async` — a client component may re-render many times, and React needs to control retries rather than let the JavaScript engine hold a suspended async frame. ## Common misreadings Candidates often say `use` "returns a promise you await" (it returns the value), or that it "runs after render like an effect" (it runs *during* render and can prevent the render from completing), or that you still need an `isLoading` flag (you do not — that is the whole point of suspending). Another frequent slip is thinking `use` only exists in Server Components; it is exported from `react` and its main job is on the client.
- Does the component have to be declared async to call use()?No. `use` is not `await`, and React function components cannot be `async` on the client. The component stays synchronous; when the promise is pending React abandons that render and simply re-runs the component from the top once the promise settles.
- What does the user see if the promise rejects instead of fulfilling?The rejection reason is thrown during render, so the nearest error boundary above the component renders its fallback UI. If no boundary exists above it, the error reaches the root and React unmounts the tree rather than showing a half-rendered page.
- Is there any render in which `data` is undefined?No. Either React suspends and the component produces no output at all, or the promise has fulfilled and `use` returns the real value. That is why the `data && …` guards typical of effect-based fetching disappear.
saying these in an interview costs you the question
- Says use() returns a promise you still have to await
- Claims the component must be declared async
- Insists you still need an isLoading state alongside use()
- Thinks use() runs after render like an effect
- Believes use() works only in Server Components