React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

react-19usepromisescontextsuspensedata-fetching

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 without useContext
  • use(promise) called conditionally inside an if block -- 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 from useContext.
  • 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 to useContext() -- 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() or new 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 use useMemo/cache to stabilize it.
  • Missing Suspense boundary -- If no <Suspense> ancestor exists when use() 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 Promise and Context are supported. Observables, generators, and other async iterables are not. Fix: Convert other async sources to promises before passing them to use().
  • 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 (React cache(), framework-level caching, or a library like SWR/TanStack Query).

Alternatives

ApproachWhen 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 / SWRFull data-fetching with caching, revalidation, pagination
useEffect + useStateLegacy fetch-on-mount pattern (still works but less ergonomic)
React.lazyCode-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 useContext only works with Context
  • use() is the preferred replacement for useContext() going forward
What happens when you pass a promise to use() without a Suspense boundary?
  • React throws an error if no <Suspense> ancestor exists when use() 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() or new 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) and useContext(MyContext) read from either syntax
Gotcha: What resource types does use() accept?
  • Only Promise and React.Context are 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>;
}