Skip to content
React rc hooks 4 min read

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:

SourceHow the promise stays stable
A Suspense-enabled frameworkNext.js, Remix, and TanStack Start provide cached loaders/fetch
A server componentThe promise is created on the server and streamed to the client
An external cache libraryReact Query, SWR, or a hand-rolled cache keyed by request
A parent that memoizes the promiseCreated 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.

AspectRegular hooks (useState, etc.)use()
Call locationTop level onlyAnywhere, including if/loops
Conditional callsNot allowedAllowed
Accepts promisesNoYes (integrates with Suspense)
Reads contextOnly useContextYes
Can be in try/catchNoNo (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 manual useEffect plus useState fetching 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/catch around use() — model them with an error boundary instead.
Last updated June 14, 2026
Was this helpful?