use is a React API that lets you read the value of a resource like a Promise or context.

const value = use(resource);

Reference

use(context)

Call use with a context to read its value. Unlike useContext, use can be called within loops and conditional statements like if.

import { use } from 'react';

function Button() {
const theme = use(ThemeContext);
// ...

See more examples below.

Parameters

Returns

The context value for the passed context, determined by the closest context provider above the calling component. If there is no provider, the returned value is the defaultValue passed to createContext.

Caveats

  • use must be called inside a Component or a Hook.
  • Reading context with use is not supported in Server Components.

use(promise)

Call use with a Promise to read its resolved value. The component calling use suspends while the Promise is pending.

import { use } from 'react';

function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
// ...

If the component that calls use is wrapped in a Suspense boundary, the fallback will be displayed while the Promise is pending. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by use. If the Promise is rejected, the fallback of the nearest Error Boundary will be displayed.

See more examples below.

Parameters

  • promise: A Promise whose resolved value you want to read. The Promise must be cached so that the same instance is reused across re-renders.

Returns

The resolved value of the Promise.

Caveats

  • use must be called inside a Component or a Hook.
  • Despite its name, use is not a Hook. Unlike Hooks, it can be called inside loops and conditional statements like if.
  • use cannot be called inside a try-catch block. Instead, wrap your component in an Error Boundary to catch the error and display a fallback.
  • Promises passed to use must be cached so the same Promise instance is reused across re-renders. See caching Promises below.
  • When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable. Data types like functions aren’t serializable and cannot be the resolved value of such a Promise.

Usage (Context)

Reading context with use

When a context is passed to use, it works similarly to useContext. While useContext must be called at the top level of your component, use can be called inside conditionals like if and loops like for.

import { use } from 'react';

function Button() {
const theme = use(ThemeContext);
// ...

use returns the context value for the context you passed. To determine the context value, React searches the component tree and finds the closest context provider above for that particular context.

To pass context to a Button, wrap it or one of its parent components into the corresponding context provider.

function MyPage() {
return (
<ThemeContext value="dark">
<Form />
</ThemeContext>
);
}

function Form() {
// ... renders buttons inside ...
}

It doesn’t matter how many layers of components there are between the provider and the Button. When a Button anywhere inside of Form calls use(ThemeContext), it will receive "dark" as the value.

Unlike useContext, use can be called in conditionals and loops like if.

function HorizontalRule({ show }) {
if (show) {
const theme = use(ThemeContext);
return <hr className={theme} />;
}
return false;
}

use is called from inside a if statement, allowing you to conditionally read values from a Context.

Pitfall

Like useContext, use(context) always looks for the closest context provider above the component that calls it. It searches upwards and does not consider context providers in the component from which you’re calling use(context).

import { createContext, use } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button show={true}>Sign up</Button>
      <Button show={false}>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = use(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    const className = 'button-' + theme;
    return (
      <button className={className}>
        {children}
      </button>
    );
  }
  return false
}


Usage (Promises)

Reading a Promise with use

Call use with a Promise to read its resolved value. The component will suspend while the Promise is pending.

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>
);
}

The component that calls use must be wrapped in a Suspense boundary. While the Promise is pending, the Suspense fallback is displayed. Once the Promise resolves, React reads the value with use and replaces the fallback with the rendered component.

Reading a Promise with use vs fetching in an Effect

Example 1 of 2:
Fetching data with use

In this example, Albums calls use with a cached Promise. The component suspends while the Promise is pending, and React displays the nearest Suspense fallback. Rejected Promises propagate to the nearest Error Boundary.

import { use, Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { fetchData } from './data.js';

export default function App() {
  return (
    <ErrorBoundary fallback={<p>Could not fetch albums.</p>}>
      <Suspense fallback={<Loading />}>
        <Albums />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

Pitfall

Promises passed to use must be cached

Promises created during render are recreated on every render, which causes React to show the Suspense fallback repeatedly and prevents content from appearing.

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));
// ...
}

Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component:

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));
Deep Dive

Why are Promises recreated on every render?

React doesn’t preserve state for renders that suspended before mounting. After each suspension, React retries rendering from scratch, so any Promise created during render is recreated.

Common ways a Promise can be unintentionally recreated during render:

function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));

// 🔴 Inline `async` function calls create a new Promise on every render.
const albums = use((async () => {
const res = await fetch('/albums');
return res.json();
})());

// 🔴 Adding `.then` returns a new Promise on every render,
// even if `fetchData` is cached.
const albums = use(fetchData('/albums').then(res => res.json()));
// ...
}

Ideally, Promises are created before rendering, such as in an event handler, a route loader, or a Server Component, and passed to the component that calls use. Fetching lazily in render delays network requests and can create waterfalls.

// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));

Caching Promises for Client Components

Promises passed to use in Client Components must be cached so the same Promise instance is reused across re-renders. If a new Promise is created directly in render, React will display the Suspense fallback on every re-render.

// ✅ Cache the Promise so the same one is reused across renders
let cache = new Map();

export function fetchData(url) {
if (!cache.has(url)) {
cache.set(url, getData(url));
}
return cache.get(url);
}

The fetchData function returns the same Promise each time it’s called with the same URL. When use receives the same Promise on a re-render, it reads the already-resolved value synchronously without suspending.

Note

The way you cache Promises depends on the framework you use with Suspense. Frameworks typically provide built-in caching mechanisms. If you don’t use a framework, you can use a simple module-level cache like the one above, or a Suspense-enabled data source.

In the example below, clicking “Re-render” updates state in App and triggers a re-render. Because fetchData returns the same cached Promise, Albums reads the value synchronously instead of showing the Suspense fallback again.

import { use, Suspense, useState } from 'react';
import { fetchData } from './data.js';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        Re-render
      </button>
      <p>Render count: {count}</p>
      <Suspense fallback={<p>Loading...</p>}>
        <Albums />
      </Suspense>
    </>
  );
}

function Albums() {
  const albums = use(fetchData('/albums'));
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

Deep Dive

How to implement a promise cache

A basic cache stores the Promise keyed by URL so the same instance is reused across renders. To also avoid unnecessary Suspense fallbacks when data is already available, you can set status and value (or reason) fields on the Promise. React checks these fields when use is called: if status is 'fulfilled', it reads value synchronously without suspending. If status is 'rejected', it throws reason. If the field is missing or 'pending', it suspends.

let cache = new Map();

function fetchData(url) {
if (!cache.has(url)) {
const promise = getData(url);
promise.status = 'pending';
promise.then(
value => {
promise.status = 'fulfilled';
promise.value = value;
},
reason => {
promise.status = 'rejected';
promise.reason = reason;
},
);
cache.set(url, promise);
}
return cache.get(url);
}

This is primarily useful for library authors building Suspense-compatible data layers. React will set the status field itself on Promises that don’t have it, but setting it yourself avoids an extra render when the data is already available.

This cache pattern is the foundation for re-fetching data (where changing the cache key triggers a new fetch) and preloading data on hover (where calling fetchData early means the Promise may already be resolved by the time use reads it).

Pitfall

Don’t conditionally call use based on whether a Promise is settled. Always call use unconditionally and let React handle reading the status field. This ensures React DevTools can show that the component may suspend on data.

// 🔴 Don't conditionally skip `use`
if (promise.status === 'fulfilled') {
return promise.value;
}
const value = use(promise);
// ✅ Always call `use` unconditionally
const value = use(promise);

Re-fetching data in Client Components

To refresh data at the same URL (for example, with a “Refresh” button), invalidate the cache entry and start a new fetch inside a startTransition. Store the resulting Promise in state to trigger a re-render. While the new Promise is pending, React keeps showing the existing content because the update is inside a Transition.

function App() {
const [albumsPromise, setAlbumsPromise] = useState(fetchData('/albums'));
const [isPending, startTransition] = useTransition();

function handleRefresh() {
startTransition(() => {
setAlbumsPromise(refetchData('/albums'));
});
}
// ...
}

refetchData clears the old cache entry and starts a new fetch at the same URL. Storing the resulting Promise in state triggers a re-render inside the Transition. On re-render, Albums receives the new Promise and use suspends on it while React keeps showing the old content.

import { Suspense, useState, useTransition } from 'react';
import { use } from 'react';
import { fetchData, refetchData } from './data.js';

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );
  const [isPending, startTransition] = useTransition();

  function handleRefresh() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <>
      <button
        onClick={handleRefresh}
        disabled={isPending}
      >
        {isPending ? 'Refreshing...' : 'Refresh'}
      </button>
      <div style={{ opacity: isPending ? 0.6 : 1 }}>
        <Suspense fallback={<Loading />}>
          <Albums albumsPromise={albumsPromise} />
        </Suspense>
      </div>
    </>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}

Note

Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. The custom cache above is useful for understanding the pattern, but in practice prefer your framework’s data fetching solution.


Preloading data on hover

You can start loading data before it’s needed by calling fetchData during a hover event. Since fetchData caches the Promise, the data may already be available by the time the user clicks. If the Promise has resolved by the time use reads it, React renders the component immediately without showing a Suspense fallback.

<button
onMouseEnter={() => fetchData(`/${id}/albums`)}
onClick={() => {
startTransition(() => {
setArtistId(id);
});
}}
>

In this example, hovering over an artist button starts fetching their albums in the background. Without hovering first, clicking shows a loading fallback. Try hovering over a button for a moment before clicking to see the difference.

import { Suspense, useState, useTransition } from 'react';
import Albums from './Albums.js';
import { fetchData } from './data.js';

export default function App() {
  const [artistId, setArtistId] = useState('the-beatles');
  const [isPending, startTransition] = useTransition();

  return (
    <>
      <div>
        {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
          <button
            key={id}
            onMouseEnter={() => {
              fetchData(`/${id}/albums`);
            }}
            onClick={() => {
              startTransition(() => {
                setArtistId(id);
              });
            }}
          >
            {id === 'the-beatles' ? 'The Beatles' :
             id === 'led-zeppelin' ? 'Led Zeppelin' :
             'Pink Floyd'}
          </button>
        ))}
      </div>
      <Suspense key={artistId} fallback={<Loading />}>
        <Albums artistId={artistId} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>Loading...</h2>;
}


Streaming data from server to client

Data can be streamed from the server to the client by passing a Promise as a prop from a Server Component to a Client Component.

import { fetchMessage } from './lib.js';
import { Message } from './message.js';

export default function App() {
const messagePromise = fetchMessage();
return (
<Suspense fallback={<p>waiting for message...</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

The Client Component then takes the Promise it received as a prop and passes it to the use API. This allows the Client Component to read the value from the Promise that was initially created by the Server Component.

// message.js
'use client';

import { use } from 'react';

export function Message({ messagePromise }) {
const messageContent = use(messagePromise);
return <p>Here is the message: {messageContent}</p>;
}

Because Message is wrapped in Suspense, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the use API and the Message component will replace the Suspense fallback.

"use client";

import { use, Suspense } from "react";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer({ messagePromise }) {
  return (
    <Suspense fallback={<p>⌛Downloading message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

Deep Dive

Should I resolve a Promise in a Server or Client Component?

A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component with the use API. You can also resolve the Promise in a Server Component with await and pass the resolved value to the Client Component as a prop. The difference comes down to how much of your UI you want to reveal before the data is ready.

Using await in a Server Component suspends the Server Component itself, so React won’t continue rendering it until the Promise resolves:

// Server Component
export default async function App() {
const messageContent = await fetchMessage();
return <Message messageContent={messageContent} />;
}

Passing the Promise to a Client Component doesn’t suspend the Server Component. The Server Component returns immediately, and the Client Component suspends when it calls use:

// Server Component
export default function App() {
const messagePromise = fetchMessage();
return <Message messagePromise={messagePromise} />;
}

In both cases, the component that reads the Promise suspends. Make sure there’s a <Suspense> boundary above it so React can display a fallback there and keep rendering the rest of the page. Without a boundary above the suspending component, the suspension bubbles up the tree and blocks the surrounding UI until the Promise resolves.

Passing the Promise down is useful when a Client Component needs the data and you want to reveal as much of the surrounding UI as possible while the Promise is pending. Because the Client Component is the component that suspends, you can place the <Suspense> boundary close to it and let the rest of the page render right away. See Displaying a fallback while content is loading for more on boundary placement.

When the surrounding UI is small or depends on the same data, prefer await on the server. If a Server Component above already awaits the data, pass the resolved value down instead of creating a new Promise to call use.


Displaying an error with an Error Boundary

If the Promise passed to use is rejected, the error propagates to the nearest Error Boundary. Wrap the component that calls use in an Error Boundary to display a fallback when the Promise is rejected.

In the example below, fetchData rejects on the first attempt and succeeds on retry. The Error Boundary catches the rejection and shows a fallback with a “Try again” button.

import { use, Suspense, useState, startTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { fetchData, refetchData } from "./data.js";

export default function App() {
  const [albumsPromise, setAlbumsPromise] = useState(
    () => fetchData('/the-beatles/albums')
  );

  function handleRetry() {
    startTransition(() => {
      setAlbumsPromise(refetchData('/the-beatles/albums'));
    });
  }

  return (
    <ErrorBoundary
      resetKeys={[albumsPromise]}
      fallbackRender={() => (
        <>
          <p>⚠️ Something went wrong loading the albums.</p>
          <button onClick={handleRetry}>Try again</button>
        </>
      )}
    >
      <Suspense fallback={<p>Loading...</p>}>
        <Albums albumsPromise={albumsPromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

function Albums({ albumsPromise }) {
  const albums = use(albumsPromise);
  return (
    <ul>
      {albums.map(album => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  );
}


Troubleshooting

I’m getting an error: “Suspense Exception: This is not a real error!”

You are calling use inside a try-catch block. use throws internally to integrate with Suspense, so it cannot be wrapped in try-catch. Instead, wrap the component that calls use in an Error Boundary to handle errors.

function Albums({ albumsPromise }) {
try {
// ❌ Don't wrap `use` in try-catch
const albums = use(albumsPromise);
} catch (e) {
return <p>Error</p>;
}
// ...

Instead, wrap the component in an Error Boundary:

function Albums({ albumsPromise }) {
// ✅ Call `use` without try-catch
const albums = use(albumsPromise);
// ...
// ✅ Use an Error Boundary to handle errors
<ErrorBoundary fallback={<p>Error</p>}>
<Albums albumsPromise={albumsPromise} />
</ErrorBoundary>

I’m getting a warning: “A component was suspended by an uncached promise”

The Promise passed to use is not cached, so React cannot reuse it across re-renders.

This commonly happens when calling fetch or an async function directly in render:

function Albums() {
// 🔴 This creates a new Promise on every render
const albums = use(fetch('/albums'));
// ...
}

To fix this, cache the Promise so the same instance is reused:

// ✅ fetchData returns the same Promise for the same URL
const albums = use(fetchData('/albums'));

See caching Promises for Client Components for more details.