The React use() API: Reading Promises and Context

The use API reads the resolved value of a Promise during render, or a context value from anywhere in a component, and pairs with Suspense and error boundaries.

7 min read

The React use API reads the resolved value of a Promise during render, or a context value from anywhere in a component. It is the client-side way to unwrap a Promise without an Effect, and a more flexible alternative to useContext for reading context. Despite its name, use is not a Hook, so it can be called inside conditions and loops.

Stable in React 19

The use API is stable since React 19. One caveat: reading context with use is not supported in Server Components. Reading a Promise with use is a client-only pattern, while Server Components unwrap Promises with await.

Reading a Promise with use

Pass a Promise to use and the component suspends until it resolves. The nearest Suspense boundary shows its fallback in the meantime, and the resolved value is returned by use, so Albums reads it directly without state or an Effect.

App.jsxApp.jsx
import { use } from "react";
 
function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>{album.title} ({album.year})</li>
      ))}
    </ul>
  );
}

Albums reads the Promise directly instead of storing it in state. Wrap it in Suspense so React has a fallback to show while the Promise is pending.

App.jsxApp.jsx
import { Suspense } from "react";
 
export default function App() {
  return (
    <Suspense fallback={<p>Loading albums...</p>}>
      <Albums albumsPromise={fetchAlbums()} />
    </Suspense>
  );
}

The list shows the loading paragraph first, then the resolved albums. There is no loading flag to set or clear: Suspense owns the pending state and use owns the read. If the Promise rejects, React walks up to the nearest error boundary instead of rendering the list.

The same call can read a context instead of a Promise, which makes use one API for two different resources. A Promise resolved before use is called can also be read synchronously if its status and value fields are set, a trick library authors use to avoid an extra fallback flash.

Promises must be cached

The Promise you pass must be the same instance across re-renders. A bare fetch call creates a new Promise on every render, which suspends again and again and never reveals content.

App.jsxApp.jsx
function Albums() {
  const albums = use(fetch("/albums"));
  return <p>{albums.length} albums</p>;
}

This reads a fresh Promise each render, so the Suspense fallback repeats forever. The fix is a cache that returns the same Promise for the same key.

App.jsxApp.jsx
const cache = new Map();
 
export function fetchData(url) {
  if (!cache.has(url)) {
    cache.set(url, getData(url));
  }
  return cache.get(url);
}

Now every render receives the same Promise. React reads the resolved value synchronously after the first suspension, and frameworks such as Next.js provide this caching for you.

The reason a fresh Promise breaks is that React retries rendering from scratch after a suspension, which recreates anything created during render. See React Suspense Explained for the boundary side of the same pattern.

Reading context with use

Pass a context to use and it returns the closest provider's value. Unlike useContext, the call can sit inside an if or a loop.

App.jsxApp.jsx
import { createContext, use } from "react";
 
const ThemeContext = createContext("light");
 
function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    return <button className={`button-${theme}`}>{children}</button>;
  }
  return null;
}

The conditional read works because use is not a Hook, so it does not break the Rules of Hooks. It still searches upward for the nearest provider and never considers the component that calls it. This is the main reason to prefer the React use API over useContext: components with early returns or nested conditionals can still read context safely.

Stream a Promise from the server

A Server Component can start a fetch and pass the Promise as a prop to a Client Component, which reads it with use. The Promise crosses the boundary without waiting for the whole page.

App.jsxApp.jsx
// Server Component
import { Message } from "./Message";
 
export default function App() {
  const messagePromise = fetchMessage();
  return (
    <Suspense fallback={<p>Waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

The Promise is created on the server and resolved on the client. The Server Component keeps fetching while the client reads the value, so only the Message subtree waits.

App.jsxApp.jsx
// Client Component
"use client";
 
import { use } from "react";
 
export function Message({ messagePromise }) {
  const content = use(messagePromise);
  return <p>Here is the message: {content}</p>;
}

The Server Component suspends only the Message subtree, so the rest of the page renders immediately. For the full model, see React Server Components Explained.

Errors and fallbacks

Do not wrap use in a try-catch. The API suspends and throws internally, so a try-catch misreads that mechanism.

Let a rejected Promise reach an error boundary instead, and keep a Suspense boundary for the pending state. An error boundary above the Suspense boundary catches the rejection, so loading and failure stay separate concerns.

If you currently load data in an Effect, the React use API removes the manual loading and error bookkeeping. See You Might Not Need an Effect for why Effect-based fetching is usually the long way around.

Rune AI

Rune AI

Key Insights

  • use reads the value of a Promise or a context.
  • It is not a Hook, so it can run in conditions and loops.
  • Pass a cached Promise, never a fresh fetch call.
  • Wrap the reader in Suspense and an error boundary.
  • Reading context with use is not supported in Server Components.
RunePowered by Rune AI

Frequently Asked Questions

Is use a Hook?

No. Despite the name, use is not a Hook. Unlike Hooks, it can be called inside conditions and loops, but it still must be called inside a component or a Hook.

Why does my component suspend forever?

The Promise passed to use is probably recreated on every render. Cache the Promise so the same instance is reused across renders, or pass it from a Server Component.

Conclusion

The use API reads a Promise or a context during render. Cache Promises so they survive re-renders, wrap readers in Suspense, and let rejected Promises reach an error boundary.