use() Hook - Read promises and context values with conditional support
Recipe
import { use, Suspense } from "react";
// Reading a promise -- must be wrapped in Suspense
function UserProfile({ userPromise }: { userPromise: Promise<{ name: string; email: string }> }) {
const user = use(userPromise);
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Reading context -- replaces useContext
const ThemeContext = React.createContext<"light" | "dark">("light");
function ThemedButton() {
const theme = use(ThemeContext);
return <button className={theme === "dark" ? "bg-gray-800 text-white" : "bg-white"}>Click me</button>;
}
// Usage
function App() {
const userPromise = fetchUser(1); // create promise outside render, or in a Server Component
return (
<ThemeContext value="dark">
<Suspense fallback={<p>Loading user...</p>}>
<UserProfile userPromise={userPromise} />
</Suspense>
<ThemedButton />
</ThemeContext>
);
}When to reach for this: Use use() to read a promise passed from a Server Component or parent, or to conditionally read context in branches where useContext cannot be called.
Working Example
// A dashboard that conditionally loads different data based on user role
"use client";
import { use, Suspense, createContext } from "react";
type User = { id: string; name: string; role: "admin" | "viewer" };
type Stats = { revenue: number; users: number; orders: number };
type Activity = { id: string; description: string; timestamp: string };
const AuthContext = createContext<User | null>(null);
function DashboardContent({
statsPromise,
activityPromise,
}: {
statsPromise: Promise<Stats>;
activityPromise: Promise<Activity[]>;
}) {
const user = use(AuthContext);
if (!user) {
return <p>Please sign in.</p>;
}
// use() can be called conditionally -- this is unique among React hooks
if (user.role === "admin") {
const stats = use(statsPromise);
return (
<div>
<h2>Admin Dashboard</h2>
<dl>
<dt>Revenue</dt><dd>${stats.revenue.toLocaleString()}</dd>
<dt>Users</dt><dd>{stats.users.toLocaleString()}</dd>
<dt>Orders</dt><dd>{stats.orders.toLocaleString()}</dd>
</dl>
</div>
);
}
const activity = use(activityPromise);
return (
<div>
<h2>Activity Feed</h2>
<ul>
{activity.map((a) => (
<li key={a.id}>
{a.description} <time>{a.timestamp}</time>
</li>
))}
</ul>
</div>
);
}
export default function Dashboard({
user,
statsPromise,
activityPromise,
}: {
user: User | null;
statsPromise: Promise<Stats>;
activityPromise: Promise<Activity[]>;
}) {
return (
<AuthContext value={user}>
<Suspense fallback={<p>Loading dashboard...</p>}>
<DashboardContent
statsPromise={statsPromise}
activityPromise={activityPromise}
/>
</Suspense>
</AuthContext>
);
}What this demonstrates:
use(AuthContext)reading context withoutuseContextuse(promise)called conditionally inside anifblock -- impossible with traditional hooks- Different promises resolved depending on the user's role
- Suspense boundary catching the pending state while promises resolve
Deep Dive
How It Works
use()is not a hook in the traditional sense. It does not follow the "rules of hooks" -- you can call it inside loops, conditionals, and early returns. This is what makes it distinct fromuseContext.- When called with a Promise,
use()suspends the component until the promise resolves. The nearest<Suspense>boundary shows the fallback. If the promise rejects, the nearest error boundary catches the error. - When called with a Context,
use()behaves identically touseContext()-- it reads the current value from the nearest provider above in the tree. - The promise you pass to
use()should be stable -- do not create a new promise on every render. Promises should come from Server Components, be cached, or be created outside the render function. use()integrates with React's concurrent features. While a promise is pending, React can continue rendering other parts of the tree.
Variations
Server Component passing a promise to a Client Component:
// app/page.tsx (Server Component)
import UserCard from "./UserCard";
export default function Page() {
// Start fetching but do NOT await -- let the client resolve it
const userPromise = fetchUser(1);
return <UserCard userPromise={userPromise} />;
}// app/UserCard.tsx
"use client";
import { use } from "react";
export default function UserCard({ userPromise }: { userPromise: Promise<{ name: string }> }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}Conditional context reading:
import { use, createContext } from "react";
const FeatureFlagContext = createContext<Set<string>>(new Set());
function FeatureGate({ flag, children }: { flag: string; children: React.ReactNode }) {
const flags = use(FeatureFlagContext);
if (!flags.has(flag)) return null;
return <>{children}</>;
}Chaining promises:
function OrderDetails({ orderPromise }: { orderPromise: Promise<{ id: string; userId: string }> }) {
const order = use(orderPromise);
// You can use the resolved value to create another promise
// but cache it to avoid re-creating on every render
const userPromise = useMemo(() => fetchUser(order.userId), [order.userId]);
const user = use(userPromise);
return (
<div>
<p>Order #{order.id}</p>
<p>Customer: {user.name}</p>
</div>
);
}TypeScript Notes
use<T>(promise: Promise<T>): T-- The resolved type is inferred from the promise.use<T>(context: React.Context<T>): T-- The context value type is inferred.- When the promise type is complex, annotate the prop:
userPromise: Promise<User>. use()is imported from"react":import { use } from "react".
Gotchas
- Creating promises during render -- Calling
fetch()ornew Promise()inside the component body creates a new promise on every render, causing infinite suspense loops. Fix: Create the promise in a Server Component, parent component, or useuseMemo/cacheto stabilize it. - Missing Suspense boundary -- If no
<Suspense>ancestor exists whenuse()reads a promise, React throws an error. Fix: Wrap the component (or an ancestor) in<Suspense fallback={...}>. - Missing error boundary -- If the promise rejects and there is no error boundary, the error propagates to the root. Fix: Add an error boundary above components that use
use()with promises. - use() cannot be called with all resource types -- Only
PromiseandContextare supported. Observables, generators, and other async iterables are not. Fix: Convert other async sources to promises before passing them touse(). - Context.Provider is deprecated -- When using
use(MyContext), note that<MyContext.Provider>still works but the preferred syntax is<MyContext value={v}>. Fix: Migrate to the shorter syntax. - Not a full data-fetching solution --
use()does not cache, deduplicate, or revalidate. It reads a single promise. Fix: Pair with a caching layer (Reactcache(), framework-level caching, or a library like SWR/TanStack Query).
Alternatives
| Approach | When to choose |
|---|---|
use(promise) | Reading a promise from a Server Component or cached source |
useContext(ctx) | Traditional context reading (still works, but cannot be called conditionally) |
use(context) | Conditional context reading in branches |
| TanStack Query / SWR | Full data-fetching with caching, revalidation, pagination |
useEffect + useState | Legacy fetch-on-mount pattern (still works but less ergonomic) |
React.lazy | Code-splitting components (different use case than data promises) |
FAQs
How does use() differ from traditional React hooks like useContext?
use()can be called inside loops, conditionals, and early returns -- it does not follow the "rules of hooks"- It accepts both Promises and Context objects, while
useContextonly works with Context use()is the preferred replacement foruseContext()going forward
What happens when you pass a promise to use() without a Suspense boundary?
- React throws an error if no
<Suspense>ancestor exists whenuse()reads a pending promise - Always wrap the component (or an ancestor) in
<Suspense fallback={...}> - If the promise rejects, the nearest error boundary catches the error
Can you call use() conditionally inside a component?
Yes. This is the key differentiator from other hooks:
function Dashboard({ user, statsPromise, activityPromise }) {
if (user.role === "admin") {
const stats = use(statsPromise);
return <AdminView stats={stats} />;
}
const activity = use(activityPromise);
return <ActivityFeed activity={activity} />;
}Why should you avoid creating promises during render?
- Calling
fetch()ornew Promise()inside the component body creates a new promise on every render - This causes infinite suspense loops because React sees a new pending promise each time
- Create promises in a Server Component, parent component, or stabilize with
useMemo/cache
How do you pass a promise from a Server Component to a Client Component?
// Server Component -- do NOT await
export default function Page() {
const userPromise = fetchUser(1);
return <UserCard userPromise={userPromise} />;
}
// Client Component
"use client";
import { use } from "react";
export default function UserCard({ userPromise }) {
const user = use(userPromise);
return <h2>{user.name}</h2>;
}Is use() a full data-fetching solution like TanStack Query or SWR?
- No.
use()does not cache, deduplicate, or revalidate data - It reads a single promise and integrates with Suspense
- Pair with a caching layer (React
cache(), framework caching, or a library) for production data-fetching needs
How do you chain dependent data fetches with use()?
function OrderDetails({ orderPromise }) {
const order = use(orderPromise);
const userPromise = useMemo(
() => fetchUser(order.userId),
[order.userId]
);
const user = use(userPromise);
return <p>Order #{order.id} by {user.name}</p>;
}Use useMemo to stabilize the dependent promise and avoid re-creation on every render.
Gotcha: Context.Provider still works but what is the preferred syntax in React 19?
<MyContext.Provider value={v}>is deprecated- Use
<MyContext value={v}>directly as the provider JSX - Both
use(MyContext)anduseContext(MyContext)read from either syntax
Gotcha: What resource types does use() accept?
- Only
PromiseandReact.Contextare supported - Observables, generators, async iterables, and other async sources are not supported
- Convert other async sources to promises before passing them to
use()
How is use() typed in TypeScript for promises vs context?
// Promise -- resolved type inferred
const user: User = use<User>(userPromise);
// Context -- value type inferred
const theme: "light" | "dark" = use(ThemeContext);Import from "react": import { use } from "react".
How do you type a component prop that accepts a promise for use() in TypeScript?
type Props = {
dataPromise: Promise<{ id: string; name: string }>;
};
function MyComponent({ dataPromise }: Props) {
const data = use(dataPromise);
return <p>{data.name}</p>;
}Related
- Server Components -- Where promises are typically created and passed down
- Form Actions -- useActionState for form-related async state
- Overview -- Full list of React 19 features