React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

react-19promisescontextsuspensehooks

use Hook

Read the value of a promise or context during render — React 19's new primitive for async data and conditional context.

Recipe

Quick-reference recipe card — copy-paste ready.

// Read a promise (suspends until resolved)
const data = use(dataPromise);
 
// Read context (works in conditions and loops — unlike useContext)
if (showTheme) {
  const theme = use(ThemeContext);
}

When to reach for this: You have a promise created outside the component (passed as a prop or from a cache) and want to read its value with Suspense, or you need to read context conditionally.

Working Example

"use client";
 
import { Suspense, use, useState } from "react";
 
interface User {
  id: number;
  name: string;
  email: string;
}
 
function fetchUser(id: number): Promise<User> {
  return fetch(`https://jsonplaceholder.typicode.com/users/${id}`).then((res) =>
    res.json()
  );
}
 
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);
 
  return (
    <div className="border rounded p-4">
      <h3 className="font-semibold">{user.name}</h3>
      <p className="text-sm text-gray-600">{user.email}</p>
    </div>
  );
}
 
export function UserLoader() {
  const [userId, setUserId] = useState(1);
  const [userPromise, setUserPromise] = useState(() => fetchUser(1));
 
  function handleLoadUser(id: number) {
    setUserId(id);
    setUserPromise(fetchUser(id));
  }
 
  return (
    <div className="space-y-3">
      <div className="flex gap-2">
        {[1, 2, 3].map((id) => (
          <button
            key={id}
            onClick={() => handleLoadUser(id)}
            className={`px-3 py-1 border rounded ${
              userId === id ? "bg-blue-600 text-white" : ""
            }`}
          >
            User {id}
          </button>
        ))}
      </div>
 
      <Suspense fallback={<p className="text-sm text-gray-500">Loading user...</p>}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
    </div>
  );
}

What this demonstrates:

  • use(userPromise) suspends UserProfile until the promise resolves
  • The Suspense boundary shows a fallback while the promise is pending
  • Creating a new promise (via setUserPromise) triggers a new suspension
  • The promise is created in the parent and passed as a prop — not created during render

Deep Dive

How It Works

  • use can read two types of values: Promises and React Contexts
  • For promises: use suspends the component until the promise resolves, then returns the resolved value. If the promise rejects, it throws to the nearest error boundary
  • For context: use reads the nearest provider's value, just like useContext, but unlike useContext, it can be called inside conditions, loops, and early returns
  • use is not technically a hook — it does not follow the "rules of hooks" regarding conditional calls. However, it can only be called during render (not in event handlers or effects)
  • The promise must be created outside the rendering component or cached — creating a new promise on every render causes infinite suspension

Parameters & Return Values

ParameterTypeDescription
resourcePromise<T> or Context<T>A promise to read, or a React context to consume
ReturnTypeDescription
valueTThe resolved value of the promise, or the current context value

Variations

Conditional context reading:

function Toolbar({ isLoggedIn }: { isLoggedIn: boolean }) {
  if (isLoggedIn) {
    const user = use(UserContext); // This is allowed — unlike useContext
    return <p>Welcome, {user.name}</p>;
  }
  return <p>Please log in</p>;
}

Reading a cached promise (common pattern):

// Cache the promise so it's not recreated on every render
const cache = new Map<string, Promise<Data>>();
 
function getData(key: string): Promise<Data> {
  if (!cache.has(key)) {
    cache.set(key, fetch(`/api/${key}`).then((r) => r.json()));
  }
  return cache.get(key)!;
}
 
function DataDisplay({ dataKey }: { dataKey: string }) {
  const data = use(getData(dataKey));
  return <div>{data.title}</div>;
}

With error boundary:

<ErrorBoundary fallback={<p>Something went wrong</p>}>
  <Suspense fallback={<Spinner />}>
    <AsyncComponent dataPromise={promise} />
  </Suspense>
</ErrorBoundary>

Server component passing promise to client component:

// Server Component
async function Page() {
  const dataPromise = fetchData(); // Don't await — pass the promise
  return (
    <Suspense fallback={<Loading />}>
      <ClientComponent dataPromise={dataPromise} />
    </Suspense>
  );
}
 
// Client Component
"use client";
function ClientComponent({ dataPromise }: { dataPromise: Promise<Data> }) {
  const data = use(dataPromise);
  return <div>{data.title}</div>;
}

TypeScript Notes

// Type is inferred from the promise or context
const user = use(userPromise);    // User (inferred from Promise<User>)
const theme = use(ThemeContext);   // Theme (inferred from Context<Theme>)
 
// Explicit typing when needed
const data = use<ApiResponse>(dataPromise);
 
// Context type narrowing works the same as useContext
const ctx = use(MyContext);
if (!ctx) throw new Error("Missing provider");

Gotchas

  • Creating promises during render — Writing use(fetch("/api/data")) inside a component creates a new promise on every render, causing infinite suspension. Fix: Create the promise outside the component, in a parent, or in a cache.

  • No rejection handling in the component — If the promise rejects, use throws the error. Without an error boundary, this crashes the app. Fix: Always wrap use-consuming components in an error boundary.

  • Not a full hookuse can be called conditionally, but it still can only be called during render. Calling it in an event handler or useEffect does not work. Fix: Call use only in the component body during render.

  • Promise identity matters — If you pass a new promise object (same URL but new fetch call), React treats it as a new resource and re-suspends. Fix: Cache or memoize your promises so the same logical request returns the same promise object.

  • Not a replacement for data fetching librariesuse handles reading a promise with Suspense, but it does not provide caching, deduplication, or revalidation. Fix: Use use with a caching layer, or use a data fetching library like TanStack Query.

Alternatives

AlternativeUse WhenDon't Use When
useEffect + useStateYou need to fetch data and manage loading/error state manuallyYou want Suspense-based loading states
useContextYou need context but don't need conditional readsYou need to read context inside conditions or loops
TanStack QueryYou need caching, deduplication, revalidation, and paginationSimple one-time data reads with Suspense
Server ComponentsData can be fetched entirely on the serverData depends on client-side state
await in Server ComponentsYou're in a server component and can directly awaitYou're in a client component

When to use use vs useEffect for data? Prefer use when you want Suspense-based streaming and the promise is created outside the rendering component. Use useEffect when you need fine-grained control over loading/error states or need to fetch based on client-side interactions.

FAQs

What types of values can the use hook read?
  • use can read two types: Promises and React Contexts.
  • For promises, it suspends the component until the promise resolves.
  • For context, it works like useContext but can be called conditionally.
Why is use not technically a hook even though it starts with "use"?
  • Unlike hooks, use does not follow the "rules of hooks" for conditional calls.
  • You can call use inside if statements, loops, and after early returns.
  • However, it can only be called during render -- not in event handlers or effects.
Gotcha: Why does use(fetch("/api/data")) cause infinite suspension?
  • Calling fetch() inside the component creates a new promise on every render.
  • React sees a new promise each time and re-suspends, creating an infinite loop.
  • Create the promise outside the component, in a parent, or in a cache.
What happens if the promise passed to use rejects?
  • use throws the error, which propagates to the nearest error boundary.
  • Without an error boundary, the rejection crashes the app.
  • Always wrap use-consuming components in an ErrorBoundary.
How do I cache a promise so it's not recreated on every render?
const cache = new Map<string, Promise<Data>>();
 
function getData(key: string): Promise<Data> {
  if (!cache.has(key)) {
    cache.set(key, fetch(`/api/${key}`).then(r => r.json()));
  }
  return cache.get(key)!;
}
 
function DataDisplay({ dataKey }: { dataKey: string }) {
  const data = use(getData(dataKey)); // same promise object
  return <div>{data.title}</div>;
}
How does use(Context) differ from useContext(Context)?
  • use(Context) can be called inside conditions, loops, and after early returns.
  • useContext(Context) must follow the rules of hooks -- top level only.
  • Both read the nearest Provider's value. Choose use when you need conditional reads.
Can I use the use hook in event handlers or useEffect?
  • No. use can only be called during render (the component body).
  • In event handlers, use await directly or manage state with useState.
  • In effects, use async/await with useEffect.
How do I type the use hook in TypeScript?
// Type is inferred from the promise or context
const user = use(userPromise);    // User (from Promise<User>)
const theme = use(ThemeContext);   // Theme (from Context<Theme>)
 
// Explicit generic when needed
const data = use<ApiResponse>(dataPromise);
Gotcha: Why does promise identity matter when using use?
  • If you pass a new promise object (same URL but new fetch() call), React treats it as a new resource.
  • This causes re-suspension even if the data hasn't changed.
  • Cache or memoize promises so the same logical request returns the same promise object.
When should I use use vs useEffect + useState for data fetching?
  • Prefer use when you want Suspense-based streaming and the promise is created outside the component.
  • Use useEffect + useState when you need fine-grained loading/error control or fetch based on client interactions.
  • use does not provide caching, deduplication, or revalidation -- pair it with a caching layer for production use.
How do I pass a promise from a server component to a client component?
// Server Component
async function Page() {
  const dataPromise = fetchData(); // Don't await
  return (
    <Suspense fallback={<Loading />}>
      <ClientComponent dataPromise={dataPromise} />
    </Suspense>
  );
}
 
// Client Component
"use client";
function ClientComponent({ dataPromise }: { dataPromise: Promise<Data> }) {
  const data = use(dataPromise);
  return <div>{data.title}</div>;
}