Menu

React use Hook: Read Promises and Context in Render

use reads the value of a promise or a context during render. With a promise it suspends until the data arrives and shows the nearest Suspense fallback. Unlike other hooks it can run inside if statements and loops.

This page includes runnable editors - edit, run, and see output instantly.

use is a React 19 API that reads a value from a promise or a context while a component renders. use(promise) pauses the component until the promise resolves, showing the nearest <Suspense> fallback in the meantime; use(context) works like useContext but may sit inside an if or a loop.

The fetchUser function below stands in for a real request.

The first render shows "Loading..." for one second, then the profile. Click User 2: the fallback comes back while the new promise is pending. Profile has no loading state of its own; it is written as if the data were already there.

How use(promise) works

When Profile calls use(userPromise):

  • If the promise has resolved, use returns its value and rendering continues.
  • If it is still pending, React stops rendering Profile and shows the fallback of the closest <Suspense> above it. When the promise settles, React renders Profile again, and this time use returns the value.
  • If it rejected, React shows the closest error boundary instead.

React remembers the result on the promise object itself. That is why the promise has to be the same object on the next render.

To keep the old profile on screen while the next one loads, set the promise inside a transition. React then waits instead of showing the fallback again:

<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>

Where to create the promise

This is the part that trips people up. Writing the fetch call inside the component that reads it looks natural and does not work:

// Do not do this
function Profile({ id }) {
    const user = use(fetchUser(id)); // a new promise on every render
    return <p>{user.name}</p>;
}

Every render calls fetchUser and gets a new, pending promise. Profile suspends. When that promise resolves, React renders Profile again, which creates another pending promise, which suspends again. The component never shows the data, and the network tab fills with requests.

Create the promise somewhere that does not re-run on every render of the reader:

  • In a parent, kept in state (as in the first example) or created in an event handler.
  • At module level, when the data does not depend on props: const configPromise = fetchConfig(); above the component.
  • In a cache keyed by the arguments, so the same id returns the same promise.
  • In a framework: a Server Component can pass a promise to a client component as a prop, and route loaders do the same. This is the case use was designed for.

Here is the cache version, so a component can ask for data by id and still get a stable promise:

Click through the three cities, then back to one you already opened. The console logs one request per city, and a city you have seen before appears without the fallback, because its promise has already resolved. Delete the if (!cache.has(city)) check and the component goes back to suspending forever.

A real cache also needs a way to expire entries. Data libraries and frameworks handle that, which is why most apps get their promises from one.

use(context), even inside an if

use(SomeContext) returns the same value as useContext(SomeContext). The difference is where you can call it. Every other hook must run at the top level, in the same order on every render (the rules of hooks). use may be called after an early return, inside a condition, or in a loop.

Click the button to switch between the two branches. Swap use(UserContext) for useContext(UserContext) and the code breaks the rules of hooks, since the hook would run on some renders and not others.

use still has limits: call it only while rendering a component or inside a custom hook. In an event handler or an effect, read the context with a value you already got during render.

Errors: error boundaries, not try/catch

When the promise rejects, the error goes to the nearest error boundary, the same way a render error does.

Open Post 2 and the boundary shows the error message. Open Post 1 again and it works: key={id} gives the boundary a fresh state for each post, so it does not keep showing the old error.

You cannot catch a pending or rejected use with try/catch. While the promise is pending, use interrupts the render by throwing a special value, and a catch around it would swallow that. The caught value is an error whose message starts "Suspense Exception: This is not a real error!", and in development React also logs "use was called from inside a try/catch block". To show a fallback value instead of an error screen, handle the rejection on the promise before passing it down:

const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');

use compared with useEffect fetching

The classic client pattern fetches in an effect and keeps three pieces of state:

function Profile({ id }) {
    const [user, setUser] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        let ignore = false;
        fetchUser(id).then(
            (u) => !ignore && setUser(u),
            (e) => !ignore && setError(e),
        );
        return () => {
            ignore = true;
        };
    }, [id]);

    if (error) return <p>Error</p>;
    if (!user) return <p>Loading...</p>;
    return <p>{user.name}</p>;
}
useEffectuse
When the request startsafter the first render is on screenwhen the promise is created, possibly before the component renders
Loading stateyour own if (!user)the nearest <Suspense>
Errorsyour own error statethe nearest error boundary
Race conditionsyou guard them (ignore)the component always reads the promise it was given
Who creates the promisethe componenta parent, a cache or a framework

The effect version is self-contained, which is why it is still the most common approach in apps without a framework; the fetching data page covers it in full. use shines when something above the component starts the request early and hands the promise down.

Frequently Asked Questions

What does the use hook do in React?

use(resource) returns the value of a promise or a context. With a promise, the component suspends until it resolves and the nearest <Suspense> shows its fallback; if it rejects, the nearest error boundary shows instead.

Can use be called conditionally?

Yes. use is the one hook you can call inside if statements, loops and after an early return. It still has to be called from a component or a hook, never from an event handler or an effect.

Why does use(fetch()) inside my component loop forever?

Each render creates a new promise. The component suspends on it, React renders again when it resolves, and that render creates another pending promise. Create the promise outside the render: in a parent's state, at module level, in a cache, or in a framework loader.

Can I wrap use in try/catch?

No. When a promise is pending, use interrupts the render by throwing, and catching that breaks it. Handle a rejected promise with an error boundary, or attach .catch() to the promise so it resolves to a fallback value.

Should I replace useEffect data fetching with use?

use needs a promise that is created once and reused, which is what frameworks and data libraries give you. In a plain client app, fetching in an effect (or with a library such as TanStack Query) is still common and fine.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED