The use() API
The use() API is a function introduced in React 19 that lets a component read the value of a resource such as a promise or a context. It is the first React API that intentionally breaks the classic “Rules of Hooks”: you can call it inside conditionals and loops. When you pass it a promise, use() suspends the component until the promise resolves and hands the result straight to Suspense and error boundaries, which makes data fetching feel synchronous without the usual useEffect plumbing.
Reading a promise
When you call use(promise), React unwraps the promise. If the promise is still pending, the component suspends and the nearest <Suspense> boundary renders its fallback. When the promise resolves, React re-renders the component and use() returns the resolved value. If it rejects, the error propagates to the nearest error boundary.
import { use, Suspense } from "react";
function fetchUser(id) {
return fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then((res) => res.json());
}
function Profile({ userPromise }) {
const user = use(userPromise);
return (
<section>
<h2>{user.name}</h2>
<p>{user.email}</p>
</section>
);
}
export default function App() {
// Create the promise outside render-sensitive code (see warning below).
const userPromise = fetchUser(1);
return (
<Suspense fallback={<p>Loading profile…</p>}>
<Profile userPromise={userPromise} />
</Suspense>
);
}
The Profile component reads the data as if it were already available. There is no loading state, no useEffect, and no useState — Suspense handles the pending UI and an error boundary handles failures.
Warning: Do not create a brand-new promise inside the body of a component that calls
use()on every render, because that produces an infinite suspend/resolve loop. The promise should come from a cache, a framework loader, or a parent that is itself wrapped in a stable mechanism.
Where the promise should come from
use() reads promises but does not create or cache them. In practice you rely on one of the following sources so the same promise is returned across renders:
| Source | How the promise stays stable |
|---|---|
| A Suspense-enabled framework | Next.js, Remix, and TanStack Start provide cached loaders/fetch |
| A server component | The promise is created on the server and streamed to the client |
| An external cache library | React Query, SWR, or a hand-rolled cache keyed by request |
| A parent that memoizes the promise | Created once and passed down as a prop |
Reading a promise created during render in a plain client component is only supported when that promise is cached between renders. Without a framework or cache, prefer passing the promise from a stable source.
Reading context
use() can also read a context value, behaving like useContext() but with one major advantage: it can be called conditionally. This lets a component skip context lookups it does not need.
import { use, createContext } from "react";
const ThemeContext = createContext("light");
function Panel({ showThemed, children }) {
if (showThemed) {
// Legal with use(): a conditional context read.
const theme = use(ThemeContext);
const className = theme === "dark" ? "panel-dark" : "panel-light";
return <div className={className}>{children}</div>;
}
return <div className="panel-plain">{children}</div>;
}
export default function App() {
return (
<ThemeContext.Provider value="dark">
<Panel showThemed={true}>Themed content</Panel>
<Panel showThemed={false}>Plain content</Panel>
</ThemeContext.Provider>
);
}
With useContext() the read would have to live at the top level of the component, even when showThemed is false. use() removes that constraint.
How use() differs from hooks
use() looks like a hook but follows different rules. The table summarizes the practical differences.
| Aspect | Regular hooks (useState, etc.) | use() |
|---|---|---|
| Call location | Top level only | Anywhere, including if/loops |
| Conditional calls | Not allowed | Allowed |
| Accepts promises | No | Yes (integrates with Suspense) |
| Reads context | Only useContext | Yes |
| Can be in try/catch | No | No (errors flow to error boundary) |
import { use } from "react";
function Comments({ commentPromises }) {
// use() inside a loop — illegal for hooks, fine here.
return (
<ul>
{commentPromises.map((promise, index) => {
const comment = use(promise);
return <li key={index}>{comment.text}</li>;
})}
</ul>
);
}
Output:
• First comment
• Second comment
• Third comment
Tip: Pair
use()with the<Suspense>boundary that should show the fallback and an error boundary that should catch rejections. The closest boundary above the suspending component wins, so place them deliberately to control loading granularity.
Best Practices
- Always wrap components that call
use(promise)in a<Suspense>boundary and an error boundary so pending and failed states are handled. - Never create the promise inside the component that consumes it unless it comes from a stable cache; create it in a loader, server component, or memoized parent.
- Prefer
use()over manualuseEffectplususeStatefetching when your stack supports Suspense data — it removes race conditions and loading flags. - Reach for conditional
use(Context)only when the read is genuinely optional; for unconditional reads,useContext()is equally fine and more familiar. - Co-locate Suspense boundaries with the UI section you want to stream so one slow request does not block unrelated content.
- Remember that errors from a rejected promise cannot be caught with
try/catcharounduse()— model them with an error boundary instead.