From d3a5b4d4aeb9871be9cc78fc0de872fdf83f895e Mon Sep 17 00:00:00 2001
From: Rick Hanlon
Date: Fri, 13 Feb 2026 13:46:35 -0500
Subject: [PATCH 1/7] Claude use docs attempt
---
src/content/reference/react/use.md | 1234 +++++++++++++++++++++++++++-
1 file changed, 1211 insertions(+), 23 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index c13ad520326..66cc6277daf 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -22,7 +22,7 @@ const value = use(resource);
Call `use` in your component to read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context).
-```jsx
+```js
import { use } from 'react';
function MessageComponent({ messagePromise }) {
@@ -33,13 +33,13 @@ function MessageComponent({ messagePromise }) {
Unlike React Hooks, `use` can be called within loops and conditional statements like `if`. Like React Hooks, the function that calls `use` must be a Component or Hook.
-When called with a Promise, the `use` API integrates with [`Suspense`](/reference/react/Suspense) and [Error Boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by the `use` API. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed.
+When called with a Promise, `use` integrates with [`Suspense`](/reference/react/Suspense) and [Error Boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by `use`. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed.
[See more examples below.](#usage)
#### Parameters {/*parameters*/}
-* `resource`: this is the source of the data you want to read a value from. A resource can be a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or a [context](/learn/passing-data-deeply-with-context).
+* `resource`: This is the source of the data you want to read a value from. A resource can be a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or a [context](/learn/passing-data-deeply-with-context).
#### Returns {/*returns*/}
@@ -47,9 +47,12 @@ The `use` API returns the value that was read from the resource like the resolve
#### Caveats {/*caveats*/}
-* The `use` API must be called inside a Component or a Hook.
+* `use` must be called inside a Component or a Hook.
+* `use` cannot be called inside a try-catch block. Instead, wrap your component in an [Error Boundary](#displaying-an-error-with-an-error-boundary), or use the Promise's [`.catch` method](#providing-an-alternative-value-with-promise-catch) to provide an alternative value.
* When fetching data in a [Server Component](/reference/rsc/server-components), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved.
-* Prefer creating Promises in [Server Components](/reference/rsc/server-components) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example](#streaming-data-from-server-to-client).
+* Prefer creating Promises in [Server Components](/reference/rsc/server-components) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example.](#streaming-data-from-server-to-client)
+* Promises passed to `use` must be stable across renders. Creating a new Promise inside a component on every render will cause React to display the Suspense fallback on every re-render. [See caching Promises below.](#caching-promises-for-client-components)
+* Reading context with `use` is not supported in [Server Components](/reference/rsc/server-components).
---
@@ -57,7 +60,7 @@ The `use` API returns the value that was read from the resource like the resolve
### Reading context with `use` {/*reading-context-with-use*/}
-When a [context](/learn/passing-data-deeply-with-context) is passed to `use`, it works similarly to [`useContext`](/reference/react/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`. `use` is preferred over `useContext` because it is more flexible.
+When a [context](/learn/passing-data-deeply-with-context) is passed to `use`, it works similarly to [`useContext`](/reference/react/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`.
```js [[2, 4, "theme"], [1, 4, "ThemeContext"]]
import { use } from 'react';
@@ -194,7 +197,240 @@ function Button({ show, children }) {
-### Streaming data from the server to the client {/*streaming-data-from-server-to-client*/}
+---
+
+### Reading a Promise with `use` {/*reading-a-promise-with-use*/}
+
+Call `use` with a Promise to read its resolved value. The component will [suspend](/reference/react/Suspense) while the Promise is pending.
+
+```js [[1, 4, "use(albumsPromise)"]]
+import { use } from 'react';
+
+function Albums({ albumsPromise }) {
+ const albums = use(albumsPromise);
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+The component that calls `use` must be wrapped in a [`Suspense`](/reference/react/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.
+
+
+
+#### Fetching data with `use` {/*fetching-data-with-use*/}
+
+Calling `use` with a cached Promise is the recommended way to fetch data. The component suspends while the Promise is pending, and React displays the nearest Suspense fallback. Rejected Promises propagate to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
+
+
+
+```js src/App.js active
+import { use, Suspense } from 'react';
+import { ErrorBoundary } from 'react-error-boundary';
+import { fetchData } from './data.js';
+
+export default function App() {
+ return (
+ Could not fetch albums.
}>
+ }>
+
+
+
+ );
+}
+
+function Albums() {
+ const albums = use(fetchData('/albums'));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+
+function Loading() {
+ return Loading... ;
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
+
+async function getData(url) {
+ if (url === '/albums') {
+ return await getAlbums();
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }];
+}
+```
+
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
+ "react-scripts": "^5.0.0",
+ "react-error-boundary": "4.0.3"
+ },
+ "main": "/index.js"
+}
+```
+
+
+
+
+
+#### Fetching data with `useEffect` {/*fetching-data-with-useeffect*/}
+
+Without `use`, a common approach is to fetch data in an Effect and update state when the data arrives. This requires managing loading and error states manually, and the component renders empty on first paint before the Effect fires.
+
+
+
+```js src/App.js active
+import { useState, useEffect } from 'react';
+import { fetchAlbums } from './data.js';
+
+export default function App() {
+ const [albums, setAlbums] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ fetchAlbums()
+ .then(data => {
+ setAlbums(data);
+ setIsLoading(false);
+ })
+ .catch(err => {
+ setError(err);
+ setIsLoading(false);
+ });
+ }, []);
+
+ if (isLoading) {
+ return Loading... ;
+ }
+
+ if (error) {
+ return Error: {error.message}
;
+ }
+
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+```js src/data.js hidden
+export async function fetchAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }];
+}
+```
+
+
+
+
+
+
+
+
+
+##### Promises passed to `use` must be stable across renders {/*promises-must-be-stable*/}
+
+Promises created inside a component are recreated on every render. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a Suspense-compatible data source. These include Suspense-enabled frameworks and client-side caching solutions such as a [`fetchData` wrapper](#caching-promises-for-client-components), or pass a Promise from a Server Component.
+
+```js
+function Albums() {
+ // 🔴 Creating a new Promise on every render
+ const albums = use(fetchAlbums());
+ // ...
+}
+```
+
+```js
+// ✅ Cache the Promise so it's stable across renders
+const albums = use(fetchData('/albums'));
+```
+
+
+
+---
+
+### Streaming data from server to client {/*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 .
@@ -316,20 +552,11 @@ But using `await` in a [Server Component](/reference/rsc/server-components) will
-### Dealing with rejected Promises {/*dealing-with-rejected-promises*/}
-
-In some cases a Promise passed to `use` could be rejected. You can handle rejected Promises by either:
-
-1. [Displaying an error to users with an Error Boundary.](#displaying-an-error-to-users-with-error-boundary)
-2. [Providing an alternative value with `Promise.catch`](#providing-an-alternative-value-with-promise-catch)
-
-
-`use` cannot be called in a try-catch block. Instead of a try-catch block [wrap your component in an Error Boundary](#displaying-an-error-to-users-with-error-boundary), or [provide an alternative value to use with the Promise's `.catch` method](#providing-an-alternative-value-with-promise-catch).
-
+---
-#### Displaying an error to users with an Error Boundary {/*displaying-an-error-to-users-with-error-boundary*/}
+### Displaying an error with an Error Boundary {/*displaying-an-error-with-an-error-boundary*/}
-If you'd like to display an error to your users when a Promise is rejected, you can use an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). To use an Error Boundary, wrap the component where you are calling the `use` API in an Error Boundary. If the Promise passed to `use` is rejected the fallback for the Error Boundary will be displayed.
+If the Promise passed to `use` is rejected, the error propagates to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). Wrap the component that calls `use` in an Error Boundary to display a fallback when the Promise is rejected.
@@ -410,9 +637,11 @@ root.render(
```
-#### Providing an alternative value with `Promise.catch` {/*providing-an-alternative-value-with-promise-catch*/}
+---
+
+### Providing an alternative value with `Promise.catch` {/*providing-an-alternative-value-with-promise-catch*/}
-If you'd like to provide an alternative value when the Promise passed to `use` is rejected you can use the Promise's [`catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) method.
+If you'd like to provide an alternative value when the Promise passed to `use` is rejected, you can use the Promise's [`catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) method.
```js [[1, 6, "catch"],[2, 7, "return"]]
import { Message } from './message.js';
@@ -436,11 +665,934 @@ To use the Promise's `catch` method, call
+
+```js src/App.js active
+import { use, Suspense, useState, useTransition } from 'react';
+import { fetchData } from './data.js';
+
+export default function App() {
+ const [artistId, setArtistId] = useState('the-beatles');
+ const [isPending, startTransition] = useTransition();
+ return (
+ <>
+
+ {
+ startTransition(() => {
+ setArtistId('the-beatles');
+ });
+ }}
+ >
+ The Beatles
+
+ {
+ startTransition(() => {
+ setArtistId('led-zeppelin');
+ });
+ }}
+ >
+ Led Zeppelin
+
+ {
+ startTransition(() => {
+ setArtistId('pink-floyd');
+ });
+ }}
+ >
+ Pink Floyd
+
+
+
+ >
+ );
+}
+
+function Albums({ artistId }) {
+ const albums = use(fetchData('/' + artistId + '/albums'));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
+
+async function getData(url) {
+ if (url === '/the-beatles/albums') {
+ return await getAlbums('the-beatles');
+ } else if (url === '/led-zeppelin/albums') {
+ return await getAlbums('led-zeppelin');
+ } else if (url === '/pink-floyd/albums') {
+ return await getAlbums('pink-floyd');
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getAlbums(artistId) {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ if (artistId === 'the-beatles') {
+ return [{
+ id: 1,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 2,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 3,
+ title: 'Yellow Submarine',
+ year: 1969
+ }];
+ } else if (artistId === 'led-zeppelin') {
+ return [{
+ id: 4,
+ title: 'Coda',
+ year: 1982
+ }, {
+ id: 5,
+ title: 'In Through the Out Door',
+ year: 1979
+ }, {
+ id: 6,
+ title: 'Presence',
+ year: 1976
+ }];
+ } else {
+ return [{
+ id: 7,
+ title: 'The Wall',
+ year: 1979
+ }, {
+ id: 8,
+ title: 'Wish You Were Here',
+ year: 1975
+ }, {
+ id: 9,
+ title: 'The Dark Side of the Moon',
+ year: 1973
+ }];
+ }
+}
+```
+
+
+
+---
+
+### Showing updated content progressively with nested Suspense {/*showing-updated-content-progressively-with-nested-suspense*/}
+
+When a page needs to load multiple pieces of data, you can use `use` in multiple components wrapped in nested [`Suspense`](/reference/react/Suspense) boundaries. Each boundary reveals its content independently as the data for that section loads, allowing the UI to fill in progressively.
+
+```js
+ }>
+
+ }>
+
+
+
+
+
+```
+
+Each component calls `use(fetchData(...))` independently. When the biography data resolves first, it appears while the albums section still shows its fallback. When the albums data resolves, it replaces the albums fallback.
+
+
+
+```js src/App.js active
+import { use, Suspense } from 'react';
+import { fetchData } from './data.js';
+
+export default function App() {
+ return (
+
+ );
+}
+
+function ArtistPage({ artist }) {
+ return (
+ <>
+ {artist.name}
+ }>
+
+ }>
+
+
+
+
+
+ >
+ );
+}
+
+function BigSpinner() {
+ return Loading... ;
+}
+
+function AlbumsGlimmer() {
+ return (
+
+ );
+}
+
+function Biography({ artistId }) {
+ const bio = use(fetchData('/' + artistId + '/bio'));
+ return (
+
+ );
+}
+
+function Panel({ children }) {
+ return (
+
+ );
+}
+
+function Albums({ artistId }) {
+ const albums = use(fetchData('/' + artistId + '/albums'));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
+
+async function getData(url) {
+ if (url === '/the-beatles/bio') {
+ return await getBio();
+ } else if (url === '/the-beatles/albums') {
+ return await getAlbums();
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getBio() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 500);
+ });
+
+ return `The Beatles were an English rock band,
+ formed in Liverpool in 1960, that comprised
+ John Lennon, Paul McCartney, George Harrison
+ and Ringo Starr.`;
+}
+
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 3000);
+ });
+
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }];
+}
+```
+
+```css
+.bio { font-style: italic; }
+
+.panel {
+ border: 1px solid #aaa;
+ border-radius: 6px;
+ margin-top: 20px;
+ padding: 10px;
+}
+
+.glimmer-panel {
+ border: 1px dashed #aaa;
+ background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%);
+ border-radius: 6px;
+ margin-top: 20px;
+ padding: 10px;
+}
+
+.glimmer-line {
+ display: block;
+ width: 60%;
+ height: 20px;
+ margin: 10px;
+ border-radius: 4px;
+ background: #f0f0f0;
+}
+```
+
+
+
+---
+
+### Re-fetching data {/*re-fetching-data*/}
+
+When you want to refresh the same data (for example, with a "Refresh" button), you can pass a version number into the cache key and trigger a re-render with [`startTransition`](/reference/react/startTransition). When `version` changes, the cache key changes, so `fetchData` creates a new Promise. While that Promise is pending, React keeps showing the existing content because the update is inside a Transition.
+
+```js
+function Albums({ version }) {
+ const albums = use(fetchData(`/albums?v=${version}`));
+ // ...
+}
+```
+
+The version is part of the cache key, so the old Promise stays cached at the old key. If a non-Transition re-render happens while the refresh is in progress, React reads the old cached Promise and avoids showing the Suspense fallback.
+
+
+
+```js src/App.js active
+import { Suspense, useState, useTransition } from 'react';
+import { use } from 'react';
+import { fetchData } from './data.js';
+
+export default function App() {
+ const [versi, setVersion] = useState(0);
+ const [isPending, startTransition] = useTransition();
+
+ function handleRefresh() {
+ startTransition(() => {
+ setVersion(v => v + 1);
+ });
+ }
+
+ return (
+ <>
+
+ {isPending ? 'Refreshing...' : 'Refresh'}
+
+
+ >
+ );
+}
+
+function Albums({ version }) {
+ const albums = use(fetchData(`/the-beatles/albums?v=${version}`));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+
+function Loading() {
+ return Loading... ;
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
+
+async function getData(url) {
+ if (url.startsWith('/the-beatles/albums')) {
+ return await getAlbums();
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }, {
+ id: 9,
+ title: 'Magical Mystery Tour',
+ year: 1967
+ }];
+}
+```
+
+```css
+button { margin-bottom: 10px; }
+```
+
+
+
+
+
+Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. Building a custom cache like the one above is useful for understanding the pattern, but in practice you should use your framework's data fetching solution.
+
+
+
+---
+
+### Preloading data on hover {/*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.
+
+```js
+ 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.
+
+
+
+```js src/App.js active
+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 (
+ <>
+
+ {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
+ {
+ fetchData(`/${id}/albums`);
+ }}
+ onClick={() => {
+ startTransition(() => {
+ setArtistId(id);
+ });
+ }}
+ >
+ {id === 'the-beatles' ? 'The Beatles' :
+ id === 'led-zeppelin' ? 'Led Zeppelin' :
+ 'Pink Floyd'}
+
+ ))}
+
+ }>
+
+
+ >
+ );
+}
+
+function Loading() {
+ return Loading... ;
+}
+```
+
+```js src/Albums.js
+import { use } from 'react';
+import { fetchData } from './data.js';
+
+export default function Albums({ artistId }) {
+ const albums = use(fetchData(`/${artistId}/albums`));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ const promise = getData(url);
+ // Set status fields so React can read the value
+ // synchronously if the Promise resolves before
+ // `use` is called (e.g. when preloading on hover).
+ 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);
+}
+
+async function getData(url) {
+ if (url.startsWith('/the-beatles/albums')) {
+ return await getAlbums('the-beatles');
+ } else if (url.startsWith('/led-zeppelin/albums')) {
+ return await getAlbums('led-zeppelin');
+ } else if (url.startsWith('/pink-floyd/albums')) {
+ return await getAlbums('pink-floyd');
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getAlbums(artistId) {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 80);
+ });
+
+ if (artistId === 'the-beatles') {
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }];
+ } else if (artistId === 'led-zeppelin') {
+ return [{
+ id: 10,
+ title: 'Coda',
+ year: 1982
+ }, {
+ id: 9,
+ title: 'In Through the Out Door',
+ year: 1979
+ }, {
+ id: 8,
+ title: 'Presence',
+ year: 1976
+ }];
+ } else {
+ return [{
+ id: 7,
+ title: 'The Wall',
+ year: 1979
+ }, {
+ id: 6,
+ title: 'Animals',
+ year: 1977
+ }, {
+ id: 5,
+ title: 'Wish You Were Here',
+ year: 1975
+ }];
+ }
+}
+```
+
+```css
+button { margin-right: 10px; }
+```
+
+
+
+---
+
+### Caching Promises for Client Components {/*caching-promises-for-client-components*/}
+
+When creating Promises in Client Components, you must cache them so a stable Promise is reused across re-renders. Creating a new Promise directly in render causes React to display the Suspense fallback on every re-render.
+
+```js
+// ✅ 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.
+
+
+
+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 library that supports Suspense-compatible caching.
+
+
+
+
+
+```js src/App.js active
+import { use, Suspense, useState } from 'react';
+import { fetchData } from './data.js';
+
+export default function App() {
+ const [count, setCount] = useState(0);
+ return (
+ <>
+ setCount(count + 1)}>
+ Re-render
+
+ Render count: {count}
+ Loading...}>
+
+
+ >
+ );
+}
+
+function Albums() {
+ const albums = use(fetchData('/albums'));
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
+```
+
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
+
+async function getData(url) {
+ if (url === '/albums') {
+ return await getAlbums();
+ } else {
+ throw Error('Not implemented');
+ }
+}
+
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
+
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }];
+}
+```
+
+
+
+---
+
+### Avoiding Suspense fallbacks for already-resolved data {/*avoiding-suspense-fallbacks-for-already-resolved-data*/}
+
+When a Promise is already settled (resolved or rejected), React can read its value immediately without suspending — but only if the Promise has `status` and `value` (or `reason`) fields set. This is because even `Promise.resolve(value)` resolves asynchronously via a microtask, so without these fields, `use` always suspends on the first render.
+
+If you're building a Suspense-enabled library, you can set these fields on the Promises you return to avoid unnecessary fallbacks:
+
+```js
+function fetchData(url) {
+ const promise = fetch(url).then(r => r.json());
+ promise.status = 'pending';
+ promise.then(
+ value => {
+ promise.status = 'fulfilled';
+ promise.value = value;
+ },
+ reason => {
+ promise.status = 'rejected';
+ promise.reason = reason;
+ },
+ );
+ return promise;
+}
+```
+
+React checks the `status` field when `use` is called. If `status` is `'fulfilled'`, it reads `value` synchronously. If `status` is `'rejected'`, it throws `reason`. If the field is missing or `'pending'`, it suspends.
+
+
+
+This is primarily useful for Suspense-enabled library authors. 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.
+
+
+
+
+
+#### Without `status` field {/*without-status-field*/}
+
+When passing a regular `Promise.resolve()` to `use`, even though the value is already known, React suspends because it can't read the value until the Promise resolves via a microtask. Click the button to see the fallback flash briefly.
+
+
+
+```js src/App.js active
+import { Suspense, use, useState } from 'react';
+
+function fetchUser(id) {
+ // Even though Promise.resolve() is "instant",
+ // the value isn't available synchronously.
+ // React must suspend until the microtask resolves.
+ return Promise.resolve({ name: `User #${id}` });
+}
+
+function UserDetails({ userPromise }) {
+ const user = use(userPromise);
+ return Hello, {user.name}!
;
+}
+
+export default function App() {
+ const [userPromise, setUserPromise] = useState(null);
+
+ return (
+
+
setUserPromise(fetchUser(1))}>
+ Show User #1
+
+
setUserPromise(fetchUser(2))}>
+ Show User #2
+
+
Loading...}>
+ {userPromise ? (
+
+ ) : (
+ Click a button to load a user.
+ )}
+
+
+ );
+}
+```
+
+
+
+
+
+#### With `status` field {/*with-status-field*/}
+
+When the Promise has `status: 'fulfilled'` and `value` set, React reads the value synchronously without suspending. No fallback is shown even though `use` is called with a Promise.
+
+
+
+```js src/App.js active
+import { Suspense, use, useState } from 'react';
+
+function fetchUser(id) {
+ const value = { name: `User #${id}` };
+ const promise = Promise.resolve(value);
+ // Setting these fields lets React read the
+ // value synchronously without suspending.
+ promise.status = 'fulfilled';
+ promise.value = value;
+ return promise;
+}
+
+function UserDetails({ userPromise }) {
+ const user = use(userPromise);
+ return Hello, {user.name}!
;
+}
+
+export default function App() {
+ const [userPromise, setUserPromise] = useState(null);
+
+ return (
+
+
setUserPromise(fetchUser(1))}>
+ Show User #1
+
+
setUserPromise(fetchUser(2))}>
+ Show User #2
+
+
Loading...}>
+ {userPromise ? (
+
+ ) : (
+ Click a button to load a user.
+ )}
+
+
+ );
+}
+```
+
+
+
+
+
+
+
+
+
+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.
+
+```js
+// 🔴 Don't conditionally skip `use`
+if (promise.status === 'fulfilled') {
+ return promise.value;
+}
+const value = use(promise);
+```
+
+```js
+// ✅ Always call `use` unconditionally
+const value = use(promise);
+```
+
+
+
+---
+
## Troubleshooting {/*troubleshooting*/}
-### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
+### I'm getting an error: "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
-You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try–catch block. If you are calling `use` inside a try–catch block, wrap your component in an Error Boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples](#dealing-with-rejected-promises).
+You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try-catch block. If you are calling `use` inside a try-catch block, wrap your component in an Error Boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples.](#displaying-an-error-with-an-error-boundary)
If you are calling `use` outside a React Component or Hook function, move the `use` call to a React Component or Hook function.
@@ -460,3 +1612,39 @@ function MessageComponent({messagePromise}) {
const message = use(messagePromise);
// ...
```
+
+---
+
+### I'm getting a warning: "A component was suspended by an uncached promise" {/*uncached-promise-error*/}
+
+This warning means you are creating a new Promise inside a component on every render instead of caching it. React needs the same Promise instance across re-renders to track its status.
+
+This commonly happens when calling an `async` function directly in render:
+
+```js
+function Albums() {
+ // 🔴 This creates a new Promise on every render
+ const albums = use(getAlbums());
+ // ...
+}
+```
+
+An `async` function always returns a new Promise, even if the underlying data is cached. To fix this, cache the Promise itself:
+
+```js
+// ✅ Cache the Promise so the same one is reused
+const albumsPromise = getAlbums(); // called once, outside render
+function Albums() {
+ const albums = use(albumsPromise);
+ // ...
+}
+```
+
+Or use a caching wrapper that returns the same Promise for the same arguments:
+
+```js
+// ✅ fetchData returns the same Promise for the same URL
+const albums = use(fetchData('/albums'));
+```
+
+See [caching Promises for Client Components](#caching-promises-for-client-components) for more details.
From b2b0a186e03035cd9bf78c738d77c1b8d868c1c2 Mon Sep 17 00:00:00 2001
From: Rick Hanlon
Date: Fri, 13 Feb 2026 13:41:06 -0500
Subject: [PATCH 2/7] Human updates
---
src/content/reference/react/use.md | 1444 ++++++++++------------------
1 file changed, 501 insertions(+), 943 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index 66cc6277daf..3890c50b551 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -18,45 +18,69 @@ const value = use(resource);
## Reference {/*reference*/}
-### `use(resource)` {/*use*/}
+### `use(context)` {/*use-context*/}
-Call `use` in your component to read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context).
+Call `use` with a [context](/learn/passing-data-deeply-with-context) to read its value. Unlike [`useContext`](/reference/react/useContext), `use` can be called within loops and conditional statements like `if`.
```js
import { use } from 'react';
-function MessageComponent({ messagePromise }) {
- const message = use(messagePromise);
+function Button() {
const theme = use(ThemeContext);
// ...
```
-Unlike React Hooks, `use` can be called within loops and conditional statements like `if`. Like React Hooks, the function that calls `use` must be a Component or Hook.
+[See more examples below.](#usage-context)
+
+#### Parameters {/*context-parameters*/}
+
+* `context`: A [context](/learn/passing-data-deeply-with-context) created with [`createContext`](/reference/react/createContext).
+
+#### Returns {/*context-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`](/reference/react/createContext).
+
+#### Caveats {/*context-caveats*/}
+
+* `use` must be called inside a Component or a Hook.
+* Reading context with `use` is not supported in [Server Components](/reference/rsc/server-components).
+
+---
+
+### `use(promise)` {/*use-promise*/}
+
+Call `use` with a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to read its resolved value. The component calling `use` *suspends* while the Promise is pending.
+
+```js
+import { use } from 'react';
+
+function MessageComponent({ messagePromise }) {
+ const message = use(messagePromise);
+ // ...
+```
-When called with a Promise, `use` integrates with [`Suspense`](/reference/react/Suspense) and [Error Boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by `use`. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed.
+If the component that calls `use` is wrapped in a [`Suspense`](/reference/react/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](/reference/react/Component#catching-rendering-errors-with-an-error-boundary) will be displayed.
-[See more examples below.](#usage)
+[See more examples below.](#usage-promises)
-#### Parameters {/*parameters*/}
+#### Parameters {/*promise-parameters*/}
-* `resource`: This is the source of the data you want to read a value from. A resource can be a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or a [context](/learn/passing-data-deeply-with-context).
+* `promise`: A [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) whose resolved value you want to read. The Promise must be [cached](#caching-promises-for-client-components) so that the same instance is reused across re-renders.
-#### Returns {/*returns*/}
+#### Returns {/*promise-returns*/}
-The `use` API returns the value that was read from the resource like the resolved value of a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context).
+The resolved value of the Promise.
-#### Caveats {/*caveats*/}
+#### Caveats {/*promise-caveats*/}
* `use` must be called inside a Component or a Hook.
-* `use` cannot be called inside a try-catch block. Instead, wrap your component in an [Error Boundary](#displaying-an-error-with-an-error-boundary), or use the Promise's [`.catch` method](#providing-an-alternative-value-with-promise-catch) to provide an alternative value.
-* When fetching data in a [Server Component](/reference/rsc/server-components), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved.
-* Prefer creating Promises in [Server Components](/reference/rsc/server-components) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example.](#streaming-data-from-server-to-client)
-* Promises passed to `use` must be stable across renders. Creating a new Promise inside a component on every render will cause React to display the Suspense fallback on every re-render. [See caching Promises below.](#caching-promises-for-client-components)
-* Reading context with `use` is not supported in [Server Components](/reference/rsc/server-components).
+* `use` cannot be called inside a try-catch block. Instead, wrap your component in an [Error Boundary](#displaying-an-error-with-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.](#caching-promises-for-client-components)
+* When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable to pass between server and client. Data types like functions aren't serializable and cannot be the resolved value of such a Promise.
---
-## Usage {/*usage*/}
+## Usage (Context) {/*usage-context*/}
### Reading context with `use` {/*reading-context-with-use*/}
@@ -199,6 +223,8 @@ function Button({ show, children }) {
---
+## Usage (Promises) {/*usage-promises*/}
+
### Reading a Promise with `use` {/*reading-a-promise-with-use*/}
Call `use` with a Promise to read its resolved value. The component will [suspend](/reference/react/Suspense) while the Promise is pending.
@@ -409,327 +435,402 @@ export async function fetchAlbums() {
-##### Promises passed to `use` must be stable across renders {/*promises-must-be-stable*/}
+##### Promises passed to `use` must be cached {/*promises-must-cached*/}
-Promises created inside a component are recreated on every render. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a Suspense-compatible data source. These include Suspense-enabled frameworks and client-side caching solutions such as a [`fetchData` wrapper](#caching-promises-for-client-components), or pass a Promise from a Server Component.
+Promises created inside a component are recreated on every render. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component.
```js
function Albums() {
- // 🔴 Creating a new Promise on every render
- const albums = use(fetchAlbums());
+ // 🔴 fetch creates a new Promise on every render.
+ const albums = use(fetch('/albums'));
// ...
}
```
```js
-// ✅ Cache the Promise so it's stable across renders
+// ✅ fetchData reads the promise from a cache.
const albums = use(fetchData('/albums'));
```
+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.
+
---
-### Streaming data from server to client {/*streaming-data-from-server-to-client*/}
+### Caching Promises for Client Components {/*caching-promises-for-client-components*/}
-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 .
+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.
-```js [[1, 4, "App"], [2, 2, "Message"], [3, 7, "Suspense"], [4, 8, "messagePromise", 30], [4, 5, "messagePromise"]]
-import { fetchMessage } from './lib.js';
-import { Message } from './message.js';
+```js
+// ✅ Cache the Promise so the same one is reused across renders
+let cache = new Map();
-export default function App() {
- const messagePromise = fetchMessage();
- return (
- waiting for message...}>
-
-
- );
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
}
```
-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.
+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.
-```js [[2, 6, "Message"], [4, 6, "messagePromise"], [4, 7, "messagePromise"], [5, 7, "use"]]
-// message.js
-'use client';
+
-import { use } from 'react';
+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 library that supports Suspense-compatible caching.
-export function Message({ messagePromise }) {
- const messageContent = use(messagePromise);
- return Here is the message: {messageContent}
;
-}
-```
-Because `Message` is wrapped in [`Suspense`](/reference/react/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.
+
-```js src/message.js active
-"use client";
-
-import { use, Suspense } from "react";
+```js src/App.js active
+import { use, Suspense, useState } from 'react';
+import { fetchData } from './data.js';
-function Message({ messagePromise }) {
- const messageContent = use(messagePromise);
- return Here is the message: {messageContent}
;
+export default function App() {
+ const [count, setCount] = useState(0);
+ return (
+ <>
+ setCount(count + 1)}>
+ Re-render
+
+ Render count: {count}
+ Loading...}>
+
+
+ >
+ );
}
-export function MessageContainer({ messagePromise }) {
+function Albums() {
+ const albums = use(fetchData('/albums'));
return (
- ⌛Downloading message...}>
-
-
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
);
}
```
-```js src/App.js hidden
-import { useState } from "react";
-import { MessageContainer } from "./message.js";
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
-function fetchMessage() {
- return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
-}
+let cache = new Map();
-export default function App() {
- const [messagePromise, setMessagePromise] = useState(null);
- const [show, setShow] = useState(false);
- function download() {
- setMessagePromise(fetchMessage());
- setShow(true);
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
}
+ return cache.get(url);
+}
- if (show) {
- return ;
+async function getData(url) {
+ if (url === '/albums') {
+ return await getAlbums();
} else {
- return Download message ;
+ throw Error('Not implemented');
}
}
-```
-
-```js src/index.js hidden
-import React, { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import './styles.css';
-// TODO: update this example to use
-// the Codesandbox Server Component
-// demo environment once it is created
-import App from './App';
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
+ });
-const root = createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }];
+}
```
-
-
-When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable to pass between server and client. Data types like functions aren't serializable and cannot be the resolved value of such a Promise.
-
-
-
-
-#### Should I resolve a Promise in a Server or Client Component? {/*resolve-promise-in-server-or-client-component*/}
+#### How to implement a promise cache {/*how-to-implement-a-promise-cache*/}
-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 required data to the Client Component as a prop.
+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.
```js
-export default async function App() {
- const messageContent = await fetchMessage();
- return
+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);
}
```
-But using `await` in a [Server Component](/reference/rsc/server-components) will block its rendering until the `await` statement is finished. Passing a Promise from a Server Component to a Client Component prevents the Promise from blocking the rendering of the Server Component.
+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](#re-fetching-data-in-client-components) (where changing the cache key triggers a new fetch) and [preloading data on hover](#preloading-data-on-hover) (where calling `fetchData` early means the Promise may already be resolved by the time `use` reads it).
----
+
-### Displaying an error with an Error Boundary {/*displaying-an-error-with-an-error-boundary*/}
+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.
-If the Promise passed to `use` is rejected, the error propagates to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). Wrap the component that calls `use` in an Error Boundary to display a fallback when the Promise is rejected.
+```js
+// 🔴 Don't conditionally skip `use`
+if (promise.status === 'fulfilled') {
+ return promise.value;
+}
+const value = use(promise);
+```
-
+```js
+// ✅ Always call `use` unconditionally
+const value = use(promise);
+```
-```js src/message.js active
-"use client";
+
-import { use, Suspense } from "react";
-import { ErrorBoundary } from "react-error-boundary";
+---
-export function MessageContainer({ messagePromise }) {
- return (
- ⚠️Something went wrong}>
- ⌛Downloading message...}>
-
-
-
- );
-}
+### Re-fetching data in Client Components {/*re-fetching-data-in-client-components*/}
-function Message({ messagePromise }) {
- const content = use(messagePromise);
- return Here is the message: {content}
;
+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`](/reference/react/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.
+
+```js
+function App() {
+ const [albumsPromise, setAlbumsPromise] = useState(fetchData('/albums'));
+ const [isPending, startTransition] = useTransition();
+
+ function handleRefresh() {
+ startTransition(() => {
+ setAlbumsPromise(refetchData('/albums'));
+ });
+ }
+ // ...
}
```
-```js src/App.js hidden
-import { useState } from "react";
-import { MessageContainer } from "./message.js";
+`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.
-function fetchMessage() {
- return new Promise((resolve, reject) => setTimeout(reject, 1000));
-}
+
+
+```js src/App.js active
+import { Suspense, useState, useTransition } from 'react';
+import { use } from 'react';
+import { fetchData, refetchData } from './data.js';
export default function App() {
- const [messagePromise, setMessagePromise] = useState(null);
- const [show, setShow] = useState(false);
- function download() {
- setMessagePromise(fetchMessage());
- setShow(true);
- }
+ const [albumsPromise, setAlbumsPromise] = useState(
+ () => fetchData('/the-beatles/albums')
+ );
+ const [isPending, startTransition] = useTransition();
- if (show) {
- return ;
- } else {
- return Download message ;
+ function handleRefresh() {
+ startTransition(() => {
+ setAlbumsPromise(refetchData('/the-beatles/albums'));
+ });
}
-}
-```
-```js src/index.js hidden
-import React, { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import './styles.css';
-
-// TODO: update this example to use
-// the Codesandbox Server Component
-// demo environment once it is created
-import App from './App';
+ return (
+ <>
+
+ {isPending ? 'Refreshing...' : 'Refresh'}
+
+
+ >
+ );
+}
-const root = createRoot(document.getElementById('root'));
-root.render(
-
-
-
-);
-```
+function Albums({ albumsPromise }) {
+ const albums = use(albumsPromise);
+ return (
+
+ {albums.map(album => (
+
+ {album.title} ({album.year})
+
+ ))}
+
+ );
+}
-```json package.json hidden
-{
- "dependencies": {
- "react": "19.0.0",
- "react-dom": "19.0.0",
- "react-scripts": "^5.0.0",
- "react-error-boundary": "4.0.3"
- },
- "main": "/index.js"
+function Loading() {
+ return Loading... ;
}
```
-
----
+```js src/data.js hidden
+// Note: the way you would do data fetching depends on
+// the framework that you use together with Suspense.
+// Normally, the caching logic would be inside a framework.
+
+let cache = new Map();
-### Providing an alternative value with `Promise.catch` {/*providing-an-alternative-value-with-promise-catch*/}
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
+ }
+ return cache.get(url);
+}
-If you'd like to provide an alternative value when the Promise passed to `use` is rejected, you can use the Promise's [`catch`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) method.
+export function refetchData(url) {
+ cache.delete(url);
+ return fetchData(url);
+}
-```js [[1, 6, "catch"],[2, 7, "return"]]
-import { Message } from './message.js';
+async function getData(url) {
+ if (url.startsWith('/the-beatles/albums')) {
+ return await getAlbums();
+ } else {
+ throw Error('Not implemented');
+ }
+}
-export default function App() {
- const messagePromise = new Promise((resolve, reject) => {
- reject();
- }).catch(() => {
- return "no new message found.";
+async function getAlbums() {
+ // Add a fake delay to make waiting noticeable.
+ await new Promise(resolve => {
+ setTimeout(resolve, 1000);
});
- return (
- waiting for message...}>
-
-
- );
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }, {
+ id: 9,
+ title: 'Magical Mystery Tour',
+ year: 1967
+ }];
}
```
-To use the Promise's `catch` method, call `catch` on the Promise object. `catch` takes a single argument: a function that takes an error message as an argument. Whatever is returned by the function passed to `catch` will be used as the resolved value of the Promise.
+```css
+button { margin-bottom: 10px; }
+```
+
+
+
+
+
+Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. Building a custom cache like the one above is useful for understanding the pattern, but in practice you should use your framework's data fetching solution.
+
+
---
-### Showing stale content with `useTransition` {/*showing-stale-content-with-usetransition*/}
+### Preloading data on hover {/*preloading-data-on-hover*/}
-When data changes (for example, when navigating between pages), you can combine `use` with [`useTransition`](/reference/react/useTransition) to keep displaying the previous content while new data loads. The `isPending` flag from `useTransition` lets you show a loading indicator while keeping old content visible.
+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.
```js
-function App({ albumsPromise }) {
- const albums = use(albumsPromise);
- const [isPending, startTransition] = useTransition();
- // ...
-}
+ fetchData(`/${id}/albums`)}
+ onClick={() => {
+ startTransition(() => {
+ setArtistId(id);
+ });
+ }}
+>
```
-When an update is wrapped in `startTransition`, React keeps showing the current content instead of switching to the Suspense fallback. The component re-renders with the new data once the Promise resolves.
+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.
```js src/App.js active
-import { use, Suspense, useState, useTransition } from 'react';
+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 (
<>
- {
- startTransition(() => {
- setArtistId('the-beatles');
- });
- }}
- >
- The Beatles
-
- {
- startTransition(() => {
- setArtistId('led-zeppelin');
- });
- }}
- >
- Led Zeppelin
-
- {
- startTransition(() => {
- setArtistId('pink-floyd');
- });
- }}
- >
- Pink Floyd
-
-
-
-
Loading...}>
-
-
+ {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
+
{
+ fetchData(`/${id}/albums`);
+ }}
+ onClick={() => {
+ startTransition(() => {
+ setArtistId(id);
+ });
+ }}
+ >
+ {id === 'the-beatles' ? 'The Beatles' :
+ id === 'led-zeppelin' ? 'Led Zeppelin' :
+ 'Pink Floyd'}
+
+ ))}
+ }>
+
+
>
);
}
-function Albums({ artistId }) {
- const albums = use(fetchData('/' + artistId + '/albums'));
+function Loading() {
+ return Loading... ;
+}
+```
+
+```js src/Albums.js
+import { use } from 'react';
+import { fetchData } from './data.js';
+
+export default function Albums({ artistId }) {
+ const albums = use(fetchData(`/${artistId}/albums`));
return (
{albums.map(album => (
@@ -751,17 +852,32 @@ let cache = new Map();
export function fetchData(url) {
if (!cache.has(url)) {
- cache.set(url, getData(url));
+ const promise = getData(url);
+ // Set status fields so React can read the value
+ // synchronously if the Promise resolves before
+ // `use` is called (e.g. when preloading on hover).
+ 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);
}
async function getData(url) {
- if (url === '/the-beatles/albums') {
+ if (url.startsWith('/the-beatles/albums')) {
return await getAlbums('the-beatles');
- } else if (url === '/led-zeppelin/albums') {
+ } else if (url.startsWith('/led-zeppelin/albums')) {
return await getAlbums('led-zeppelin');
- } else if (url === '/pink-floyd/albums') {
+ } else if (url.startsWith('/pink-floyd/albums')) {
return await getAlbums('pink-floyd');
} else {
throw Error('Not implemented');
@@ -771,34 +887,34 @@ async function getData(url) {
async function getAlbums(artistId) {
// Add a fake delay to make waiting noticeable.
await new Promise(resolve => {
- setTimeout(resolve, 1000);
+ setTimeout(resolve, 80);
});
if (artistId === 'the-beatles') {
return [{
- id: 1,
+ id: 13,
title: 'Let It Be',
year: 1970
}, {
- id: 2,
+ id: 12,
title: 'Abbey Road',
year: 1969
}, {
- id: 3,
+ id: 11,
title: 'Yellow Submarine',
year: 1969
}];
} else if (artistId === 'led-zeppelin') {
return [{
- id: 4,
+ id: 10,
title: 'Coda',
year: 1982
}, {
- id: 5,
+ id: 9,
title: 'In Through the Out Door',
year: 1979
}, {
- id: 6,
+ id: 8,
title: 'Presence',
year: 1976
}];
@@ -808,261 +924,183 @@ async function getAlbums(artistId) {
title: 'The Wall',
year: 1979
}, {
- id: 8,
+ id: 6,
+ title: 'Animals',
+ year: 1977
+ }, {
+ id: 5,
title: 'Wish You Were Here',
year: 1975
- }, {
- id: 9,
- title: 'The Dark Side of the Moon',
- year: 1973
}];
}
}
```
+```css
+button { margin-right: 10px; }
+```
+
---
-### Showing updated content progressively with nested Suspense {/*showing-updated-content-progressively-with-nested-suspense*/}
+### Streaming data from server to client {/*streaming-data-from-server-to-client*/}
-When a page needs to load multiple pieces of data, you can use `use` in multiple components wrapped in nested [`Suspense`](/reference/react/Suspense) boundaries. Each boundary reveals its content independently as the data for that section loads, allowing the UI to fill in progressively.
+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.
```js
- }>
-
- }>
-
-
-
-
-
-```
-
-Each component calls `use(fetchData(...))` independently. When the biography data resolves first, it appears while the albums section still shows its fallback. When the albums data resolves, it replaces the albums fallback.
-
-
-
-```js src/App.js active
-import { use, Suspense } from 'react';
-import { fetchData } from './data.js';
+import { fetchMessage } from './lib.js';
+import { Message } from './message.js';
export default function App() {
+ const messagePromise = fetchMessage();
return (
-
+ waiting for message...}>
+
+
);
}
+```
-function ArtistPage({ artist }) {
- return (
- <>
- {artist.name}
- }>
-
- }>
-
-
-
-
-
- >
- );
-}
+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.
-function BigSpinner() {
- return Loading... ;
-}
+```js
+// message.js
+'use client';
-function AlbumsGlimmer() {
- return (
-
- );
-}
+import { use } from 'react';
-function Biography({ artistId }) {
- const bio = use(fetchData('/' + artistId + '/bio'));
- return (
-
- );
+export function Message({ messagePromise }) {
+ const messageContent = use(messagePromise);
+ return Here is the message: {messageContent}
;
}
+```
+Because `Message` is wrapped in [`Suspense`](/reference/react/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.
-function Panel({ children }) {
- return (
-
- );
+
+
+```js src/message.js active
+"use client";
+
+import { use, Suspense } from "react";
+
+function Message({ messagePromise }) {
+ const messageContent = use(messagePromise);
+ return Here is the message: {messageContent}
;
}
-function Albums({ artistId }) {
- const albums = use(fetchData('/' + artistId + '/albums'));
+export function MessageContainer({ messagePromise }) {
return (
-
- {albums.map(album => (
-
- {album.title} ({album.year})
-
- ))}
-
+ ⌛Downloading message...}>
+
+
);
}
```
-```js src/data.js hidden
-// Note: the way you would do data fetching depends on
-// the framework that you use together with Suspense.
-// Normally, the caching logic would be inside a framework.
+```js src/App.js hidden
+import { useState } from "react";
+import { MessageContainer } from "./message.js";
-let cache = new Map();
+function fetchMessage() {
+ return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
+}
-export function fetchData(url) {
- if (!cache.has(url)) {
- cache.set(url, getData(url));
+export default function App() {
+ const [messagePromise, setMessagePromise] = useState(null);
+ const [show, setShow] = useState(false);
+ function download() {
+ setMessagePromise(fetchMessage());
+ setShow(true);
}
- return cache.get(url);
-}
-async function getData(url) {
- if (url === '/the-beatles/bio') {
- return await getBio();
- } else if (url === '/the-beatles/albums') {
- return await getAlbums();
+ if (show) {
+ return ;
} else {
- throw Error('Not implemented');
+ return Download message ;
}
}
-
-async function getBio() {
- // Add a fake delay to make waiting noticeable.
- await new Promise(resolve => {
- setTimeout(resolve, 500);
- });
-
- return `The Beatles were an English rock band,
- formed in Liverpool in 1960, that comprised
- John Lennon, Paul McCartney, George Harrison
- and Ringo Starr.`;
-}
-
-async function getAlbums() {
- // Add a fake delay to make waiting noticeable.
- await new Promise(resolve => {
- setTimeout(resolve, 3000);
- });
-
- return [{
- id: 13,
- title: 'Let It Be',
- year: 1970
- }, {
- id: 12,
- title: 'Abbey Road',
- year: 1969
- }, {
- id: 11,
- title: 'Yellow Submarine',
- year: 1969
- }, {
- id: 10,
- title: 'The Beatles',
- year: 1968
- }];
-}
```
-```css
-.bio { font-style: italic; }
-
-.panel {
- border: 1px solid #aaa;
- border-radius: 6px;
- margin-top: 20px;
- padding: 10px;
-}
+```js src/index.js hidden
+import React, { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import './styles.css';
-.glimmer-panel {
- border: 1px dashed #aaa;
- background: linear-gradient(90deg, rgba(221,221,221,1) 0%, rgba(255,255,255,1) 100%);
- border-radius: 6px;
- margin-top: 20px;
- padding: 10px;
-}
+// TODO: update this example to use
+// the Codesandbox Server Component
+// demo environment once it is created
+import App from './App';
-.glimmer-line {
- display: block;
- width: 60%;
- height: 20px;
- margin: 10px;
- border-radius: 4px;
- background: #f0f0f0;
-}
+const root = createRoot(document.getElementById('root'));
+root.render(
+
+
+
+);
```
----
-### Re-fetching data {/*re-fetching-data*/}
+
+
+#### Should I resolve a Promise in a Server or Client Component? {/*resolve-promise-in-server-or-client-component*/}
-When you want to refresh the same data (for example, with a "Refresh" button), you can pass a version number into the cache key and trigger a re-render with [`startTransition`](/reference/react/startTransition). When `version` changes, the cache key changes, so `fetchData` creates a new Promise. While that Promise is pending, React keeps showing the existing content because the update is inside a Transition.
+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 required data to the Client Component as a prop.
```js
-function Albums({ version }) {
- const albums = use(fetchData(`/albums?v=${version}`));
- // ...
+export default async function App() {
+ const messageContent = await fetchMessage();
+ return
}
```
-The version is part of the cache key, so the old Promise stays cached at the old key. If a non-Transition re-render happens while the refresh is in progress, React reads the old cached Promise and avoids showing the Suspense fallback.
+But using `await` in a [Server Component](/reference/rsc/server-components) will block its rendering until the `await` statement is finished. Passing a Promise from a Server Component to a Client Component prevents the Promise from blocking the rendering of the Server Component.
+
+
+
+---
+
+### Displaying an error with an Error Boundary {/*displaying-an-error-with-an-error-boundary*/}
+
+If the Promise passed to `use` is rejected, the error propagates to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). Wrap the component that calls `use` in an Error Boundary to display a fallback when the Promise is rejected.
```js src/App.js active
-import { Suspense, useState, useTransition } from 'react';
-import { use } from 'react';
-import { fetchData } from './data.js';
+import { use, Suspense, useState, useTransition } from "react";
+import { ErrorBoundary } from "react-error-boundary";
+import { fetchData } from "./data.js";
export default function App() {
- const [versi, setVersion] = useState(0);
+ const [albumsPromise, setAlbumsPromise] = useState(
+ () => fetchData('/the-beatles/albums')
+ );
const [isPending, startTransition] = useTransition();
function handleRefresh() {
startTransition(() => {
- setVersion(v => v + 1);
+ setAlbumsPromise(fetchData('/the-beatles/albums'));
});
}
return (
<>
-
+
{isPending ? 'Refreshing...' : 'Refresh'}
-
-
}>
-
+
⚠️Something went wrong}>
+ Loading...}>
+
-
+
>
);
}
-function Albums({ version }) {
- const albums = use(fetchData(`/the-beatles/albums?v=${version}`));
+function Albums({ albumsPromise }) {
+ const albums = use(albumsPromise);
return (
{albums.map(album => (
@@ -1073,10 +1111,6 @@ function Albums({ version }) {
);
}
-
-function Loading() {
- return Loading... ;
-}
```
```js src/data.js hidden
@@ -1084,563 +1118,87 @@ function Loading() {
// the framework that you use together with Suspense.
// Normally, the caching logic would be inside a framework.
-let cache = new Map();
-
-export function fetchData(url) {
- if (!cache.has(url)) {
- cache.set(url, getData(url));
- }
- return cache.get(url);
-}
-
async function getData(url) {
- if (url.startsWith('/the-beatles/albums')) {
- return await getAlbums();
+ if (url === '/the-beatles/albums') {
+ // This fetch will always fail to demonstrate the error boundary.
+ throw new Error('Failed to fetch albums');
} else {
throw Error('Not implemented');
}
}
-async function getAlbums() {
- // Add a fake delay to make waiting noticeable.
- await new Promise(resolve => {
- setTimeout(resolve, 1000);
- });
-
- return [{
- id: 13,
- title: 'Let It Be',
- year: 1970
- }, {
- id: 12,
- title: 'Abbey Road',
- year: 1969
- }, {
- id: 11,
- title: 'Yellow Submarine',
- year: 1969
- }, {
- id: 10,
- title: 'The Beatles',
- year: 1968
- }, {
- id: 9,
- title: 'Magical Mystery Tour',
- year: 1967
- }];
+export function fetchData(url) {
+ return getData(url);
}
```
-```css
-button { margin-bottom: 10px; }
+```json package.json hidden
+{
+ "dependencies": {
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
+ "react-scripts": "^5.0.0",
+ "react-error-boundary": "4.0.3"
+ },
+ "main": "/index.js"
+}
```
-
-
+---
-Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. Building a custom cache like the one above is useful for understanding the pattern, but in practice you should use your framework's data fetching solution.
+---
-
+## Troubleshooting {/*troubleshooting*/}
----
+### I'm getting an error: "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
-### Preloading data on hover {/*preloading-data-on-hover*/}
+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](#displaying-an-error-with-an-error-boundary) to handle errors.
-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.
+```jsx
+function Albums({ albumsPromise }) {
+ try {
+ // ❌ Don't wrap `use` in try-catch
+ const albums = use(albumsPromise);
+ } catch (e) {
+ return Error
;
+ }
+ // ...
+```
-```js
- fetchData(`/${id}/albums`)}
- onClick={() => {
- startTransition(() => {
- setArtistId(id);
- });
- }}
->
+Instead, wrap the component in an Error Boundary:
+
+```jsx
+function Albums({ albumsPromise }) {
+ // ✅ Call `use` without try-catch
+ const albums = use(albumsPromise);
+ // ...
```
-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.
+```jsx
+// ✅ Use an Error Boundary to handle errors
+Error}>
+
+
+```
-
+---
-```js src/App.js active
-import { Suspense, useState, useTransition } from 'react';
-import Albums from './Albums.js';
-import { fetchData } from './data.js';
+### I'm getting a warning: "A component was suspended by an uncached promise" {/*uncached-promise-error*/}
-export default function App() {
- const [artistId, setArtistId] = useState('the-beatles');
- const [isPending, startTransition] = useTransition();
+The Promise passed to `use` is not cached, so React cannot reuse it across re-renders.
- return (
- <>
-
- {['the-beatles', 'led-zeppelin', 'pink-floyd'].map(id => (
- {
- fetchData(`/${id}/albums`);
- }}
- onClick={() => {
- startTransition(() => {
- setArtistId(id);
- });
- }}
- >
- {id === 'the-beatles' ? 'The Beatles' :
- id === 'led-zeppelin' ? 'Led Zeppelin' :
- 'Pink Floyd'}
-
- ))}
-
- }>
-
-
- >
- );
-}
-
-function Loading() {
- return Loading... ;
-}
-```
-
-```js src/Albums.js
-import { use } from 'react';
-import { fetchData } from './data.js';
-
-export default function Albums({ artistId }) {
- const albums = use(fetchData(`/${artistId}/albums`));
- return (
-
- {albums.map(album => (
-
- {album.title} ({album.year})
-
- ))}
-
- );
-}
-```
-
-```js src/data.js hidden
-// Note: the way you would do data fetching depends on
-// the framework that you use together with Suspense.
-// Normally, the caching logic would be inside a framework.
-
-let cache = new Map();
-
-export function fetchData(url) {
- if (!cache.has(url)) {
- const promise = getData(url);
- // Set status fields so React can read the value
- // synchronously if the Promise resolves before
- // `use` is called (e.g. when preloading on hover).
- 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);
-}
-
-async function getData(url) {
- if (url.startsWith('/the-beatles/albums')) {
- return await getAlbums('the-beatles');
- } else if (url.startsWith('/led-zeppelin/albums')) {
- return await getAlbums('led-zeppelin');
- } else if (url.startsWith('/pink-floyd/albums')) {
- return await getAlbums('pink-floyd');
- } else {
- throw Error('Not implemented');
- }
-}
-
-async function getAlbums(artistId) {
- // Add a fake delay to make waiting noticeable.
- await new Promise(resolve => {
- setTimeout(resolve, 80);
- });
-
- if (artistId === 'the-beatles') {
- return [{
- id: 13,
- title: 'Let It Be',
- year: 1970
- }, {
- id: 12,
- title: 'Abbey Road',
- year: 1969
- }, {
- id: 11,
- title: 'Yellow Submarine',
- year: 1969
- }];
- } else if (artistId === 'led-zeppelin') {
- return [{
- id: 10,
- title: 'Coda',
- year: 1982
- }, {
- id: 9,
- title: 'In Through the Out Door',
- year: 1979
- }, {
- id: 8,
- title: 'Presence',
- year: 1976
- }];
- } else {
- return [{
- id: 7,
- title: 'The Wall',
- year: 1979
- }, {
- id: 6,
- title: 'Animals',
- year: 1977
- }, {
- id: 5,
- title: 'Wish You Were Here',
- year: 1975
- }];
- }
-}
-```
-
-```css
-button { margin-right: 10px; }
-```
-
-
-
----
-
-### Caching Promises for Client Components {/*caching-promises-for-client-components*/}
-
-When creating Promises in Client Components, you must cache them so a stable Promise is reused across re-renders. Creating a new Promise directly in render causes React to display the Suspense fallback on every re-render.
-
-```js
-// ✅ 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.
-
-
-
-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 library that supports Suspense-compatible caching.
-
-
-
-
-
-```js src/App.js active
-import { use, Suspense, useState } from 'react';
-import { fetchData } from './data.js';
-
-export default function App() {
- const [count, setCount] = useState(0);
- return (
- <>
- setCount(count + 1)}>
- Re-render
-
- Render count: {count}
- Loading...}>
-
-
- >
- );
-}
-
-function Albums() {
- const albums = use(fetchData('/albums'));
- return (
-
- {albums.map(album => (
-
- {album.title} ({album.year})
-
- ))}
-
- );
-}
-```
-
-```js src/data.js hidden
-// Note: the way you would do data fetching depends on
-// the framework that you use together with Suspense.
-// Normally, the caching logic would be inside a framework.
-
-let cache = new Map();
-
-export function fetchData(url) {
- if (!cache.has(url)) {
- cache.set(url, getData(url));
- }
- return cache.get(url);
-}
-
-async function getData(url) {
- if (url === '/albums') {
- return await getAlbums();
- } else {
- throw Error('Not implemented');
- }
-}
-
-async function getAlbums() {
- // Add a fake delay to make waiting noticeable.
- await new Promise(resolve => {
- setTimeout(resolve, 1000);
- });
-
- return [{
- id: 13,
- title: 'Let It Be',
- year: 1970
- }, {
- id: 12,
- title: 'Abbey Road',
- year: 1969
- }, {
- id: 11,
- title: 'Yellow Submarine',
- year: 1969
- }];
-}
-```
-
-
-
----
-
-### Avoiding Suspense fallbacks for already-resolved data {/*avoiding-suspense-fallbacks-for-already-resolved-data*/}
-
-When a Promise is already settled (resolved or rejected), React can read its value immediately without suspending — but only if the Promise has `status` and `value` (or `reason`) fields set. This is because even `Promise.resolve(value)` resolves asynchronously via a microtask, so without these fields, `use` always suspends on the first render.
-
-If you're building a Suspense-enabled library, you can set these fields on the Promises you return to avoid unnecessary fallbacks:
-
-```js
-function fetchData(url) {
- const promise = fetch(url).then(r => r.json());
- promise.status = 'pending';
- promise.then(
- value => {
- promise.status = 'fulfilled';
- promise.value = value;
- },
- reason => {
- promise.status = 'rejected';
- promise.reason = reason;
- },
- );
- return promise;
-}
-```
-
-React checks the `status` field when `use` is called. If `status` is `'fulfilled'`, it reads `value` synchronously. If `status` is `'rejected'`, it throws `reason`. If the field is missing or `'pending'`, it suspends.
-
-
-
-This is primarily useful for Suspense-enabled library authors. 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.
-
-
-
-
-
-#### Without `status` field {/*without-status-field*/}
-
-When passing a regular `Promise.resolve()` to `use`, even though the value is already known, React suspends because it can't read the value until the Promise resolves via a microtask. Click the button to see the fallback flash briefly.
-
-
-
-```js src/App.js active
-import { Suspense, use, useState } from 'react';
-
-function fetchUser(id) {
- // Even though Promise.resolve() is "instant",
- // the value isn't available synchronously.
- // React must suspend until the microtask resolves.
- return Promise.resolve({ name: `User #${id}` });
-}
-
-function UserDetails({ userPromise }) {
- const user = use(userPromise);
- return Hello, {user.name}!
;
-}
-
-export default function App() {
- const [userPromise, setUserPromise] = useState(null);
-
- return (
-
-
setUserPromise(fetchUser(1))}>
- Show User #1
-
-
setUserPromise(fetchUser(2))}>
- Show User #2
-
-
Loading...}>
- {userPromise ? (
-
- ) : (
- Click a button to load a user.
- )}
-
-
- );
-}
-```
-
-
-
-
-
-#### With `status` field {/*with-status-field*/}
-
-When the Promise has `status: 'fulfilled'` and `value` set, React reads the value synchronously without suspending. No fallback is shown even though `use` is called with a Promise.
-
-
-
-```js src/App.js active
-import { Suspense, use, useState } from 'react';
-
-function fetchUser(id) {
- const value = { name: `User #${id}` };
- const promise = Promise.resolve(value);
- // Setting these fields lets React read the
- // value synchronously without suspending.
- promise.status = 'fulfilled';
- promise.value = value;
- return promise;
-}
-
-function UserDetails({ userPromise }) {
- const user = use(userPromise);
- return Hello, {user.name}!
;
-}
-
-export default function App() {
- const [userPromise, setUserPromise] = useState(null);
-
- return (
-
-
setUserPromise(fetchUser(1))}>
- Show User #1
-
-
setUserPromise(fetchUser(2))}>
- Show User #2
-
-
Loading...}>
- {userPromise ? (
-
- ) : (
- Click a button to load a user.
- )}
-
-
- );
-}
-```
-
-
-
-
-
-
-
-
-
-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.
-
-```js
-// 🔴 Don't conditionally skip `use`
-if (promise.status === 'fulfilled') {
- return promise.value;
-}
-const value = use(promise);
-```
-
-```js
-// ✅ Always call `use` unconditionally
-const value = use(promise);
-```
-
-
-
----
-
-## Troubleshooting {/*troubleshooting*/}
-
-### I'm getting an error: "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
-
-You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try-catch block. If you are calling `use` inside a try-catch block, wrap your component in an Error Boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples.](#displaying-an-error-with-an-error-boundary)
-
-If you are calling `use` outside a React Component or Hook function, move the `use` call to a React Component or Hook function.
-
-```jsx
-function MessageComponent({messagePromise}) {
- function download() {
- // ❌ the function calling `use` is not a Component or Hook
- const message = use(messagePromise);
- // ...
-```
-
-Instead, call `use` outside any component closures, where the function that calls `use` is a Component or Hook.
-
-```jsx
-function MessageComponent({messagePromise}) {
- // ✅ `use` is being called from a component.
- const message = use(messagePromise);
- // ...
-```
-
----
-
-### I'm getting a warning: "A component was suspended by an uncached promise" {/*uncached-promise-error*/}
-
-This warning means you are creating a new Promise inside a component on every render instead of caching it. React needs the same Promise instance across re-renders to track its status.
-
-This commonly happens when calling an `async` function directly in render:
+This commonly happens when calling `fetch` or an `async` function directly in render:
```js
function Albums() {
// 🔴 This creates a new Promise on every render
- const albums = use(getAlbums());
- // ...
-}
-```
-
-An `async` function always returns a new Promise, even if the underlying data is cached. To fix this, cache the Promise itself:
-
-```js
-// ✅ Cache the Promise so the same one is reused
-const albumsPromise = getAlbums(); // called once, outside render
-function Albums() {
- const albums = use(albumsPromise);
+ const albums = use(fetch('/albums'));
// ...
}
```
-Or use a caching wrapper that returns the same Promise for the same arguments:
+To fix this, cache the Promise so the same instance is reused:
```js
// ✅ fetchData returns the same Promise for the same URL
From d93aa52bfba42c354370829f032e7321d6aabf28 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Fri, 22 May 2026 01:01:04 +0200
Subject: [PATCH 3/7] Address review feedback
---
src/content/reference/react/use.md | 136 +++++++++++++++++++++--------
1 file changed, 101 insertions(+), 35 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index 3890c50b551..b352ca91be3 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -74,9 +74,10 @@ The resolved value of the Promise.
#### Caveats {/*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](#displaying-an-error-with-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.](#caching-promises-for-client-components)
-* When passing a Promise from a Server Component to a Client Component, its resolved value must be serializable to pass between server and client. Data types like functions aren't serializable and cannot be the resolved value of such a Promise.
+* When passing a Promise from a Server Component to a Client Component, its resolved value must be [serializable](/reference/rsc/use-client#serializable-types). Data types like functions aren't serializable and cannot be the resolved value of such a Promise.
---
@@ -252,7 +253,7 @@ The component that calls `use` must be wrapped in
#### Fetching data with `use` {/*fetching-data-with-use*/}
-Calling `use` with a cached Promise is the recommended way to fetch data. The component suspends while the Promise is pending, and React displays the nearest Suspense fallback. Rejected Promises propagate to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
+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](/reference/react/Component#catching-rendering-errors-with-an-error-boundary).
@@ -355,7 +356,7 @@ async function getAlbums() {
#### Fetching data with `useEffect` {/*fetching-data-with-useeffect*/}
-Without `use`, a common approach is to fetch data in an Effect and update state when the data arrives. This requires managing loading and error states manually, and the component renders empty on first paint before the Effect fires.
+Before `use`, a common approach was to fetch data in an Effect and update state when the data arrives. Compared to `use`, this approach requires managing loading and error states manually. For more details on why fetching in an Effect is discouraged, see [You Might Not Need an Effect](/learn/you-might-not-need-an-effect#fetching-data).
@@ -439,10 +440,19 @@ export async function fetchAlbums() {
Promises created inside a component are recreated on every render. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component.
+Common ways a Promise can be unintentionally recreated on every render:
+
```js
function Albums() {
- // 🔴 fetch creates a new Promise on every render.
+ // 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));
+
+ // 🔴 Calling an async function creates a new Promise on every render.
+ const albums = use(getAlbums());
+
+ // 🔴 Adding `.then` returns a new Promise on every render,
+ // even if `fetchData` is cached.
+ const albums = use(fetchData('/albums').then(res => res.json()));
// ...
}
```
@@ -452,7 +462,7 @@ function Albums() {
const albums = use(fetchData('/albums'));
```
-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.
+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.
@@ -478,10 +488,12 @@ The `fetchData` function returns the same Promise each time it's called with the
-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 library that supports Suspense-compatible caching.
+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](/reference/react/Suspense#displaying-a-fallback-while-content-is-loading).
+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.
+
```js src/App.js active
@@ -567,7 +579,7 @@ async function getAlbums() {
#### How to implement a promise cache {/*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.
+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.
```js
let cache = new Map();
@@ -758,7 +770,7 @@ button { margin-bottom: 10px; }
-Frameworks that support Suspense typically provide their own caching and invalidation mechanisms. Building a custom cache like the one above is useful for understanding the pattern, but in practice you should use your framework's data fetching solution.
+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.
@@ -887,7 +899,7 @@ async function getData(url) {
async function getAlbums(artistId) {
// Add a fake delay to make waiting noticeable.
await new Promise(resolve => {
- setTimeout(resolve, 80);
+ setTimeout(resolve, 800);
});
if (artistId === 'the-beatles') {
@@ -1047,16 +1059,33 @@ root.render(
#### Should I resolve a Promise in a Server or Client Component? {/*resolve-promise-in-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 required data to the Client Component as a prop.
+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](/reference/rsc/server-components) suspends the Server Component itself, so React won't continue rendering it until the Promise resolves:
```js
+// Server Component
export default async function App() {
const messageContent = await fetchMessage();
- return
+ return ;
+}
+```
+
+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`:
+
+```js
+// Server Component
+export default function App() {
+ const messagePromise = fetchMessage();
+ return ;
}
```
-But using `await` in a [Server Component](/reference/rsc/server-components) will block its rendering until the `await` statement is finished. Passing a Promise from a Server Component to a Client Component prevents the Promise from blocking the rendering of the Server Component.
+In both cases, the component that reads the Promise suspends. Make sure there's a [``](/reference/react/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 `` boundary close to it and let the rest of the page render right away. See [Displaying a fallback while content is loading](/reference/react/Suspense#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`.
@@ -1066,36 +1095,40 @@ But using `await` in a [Server Component](/reference/rsc/server-components) will
If the Promise passed to `use` is rejected, the error propagates to the nearest [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-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.
+
```js src/App.js active
-import { use, Suspense, useState, useTransition } from "react";
+import { use, Suspense, useState, startTransition } from "react";
import { ErrorBoundary } from "react-error-boundary";
-import { fetchData } from "./data.js";
+import { fetchData, refetchData } from "./data.js";
export default function App() {
const [albumsPromise, setAlbumsPromise] = useState(
() => fetchData('/the-beatles/albums')
);
- const [isPending, startTransition] = useTransition();
- function handleRefresh() {
+ function handleRetry() {
startTransition(() => {
- setAlbumsPromise(fetchData('/the-beatles/albums'));
+ setAlbumsPromise(refetchData('/the-beatles/albums'));
});
}
return (
- <>
-
- {isPending ? 'Refreshing...' : 'Refresh'}
-
- ⚠️Something went wrong}>
- Loading...}>
-
-
-
- >
+ (
+ <>
+ ⚠️ Something went wrong loading the albums.
+ Try again
+ >
+ )}
+ >
+ Loading...}>
+
+
+
);
}
@@ -1118,17 +1151,50 @@ function Albums({ albumsPromise }) {
// the framework that you use together with Suspense.
// Normally, the caching logic would be inside a framework.
-async function getData(url) {
- if (url === '/the-beatles/albums') {
- // This fetch will always fail to demonstrate the error boundary.
- throw new Error('Failed to fetch albums');
- } else {
- throw Error('Not implemented');
+let cache = new Map();
+let retried = false;
+
+export function fetchData(url) {
+ if (!cache.has(url)) {
+ cache.set(url, getData(url));
}
+ return cache.get(url);
}
-export function fetchData(url) {
- return getData(url);
+export function refetchData(url) {
+ cache.delete(url);
+ retried = true;
+ return fetchData(url);
+}
+
+async function getData(url) {
+ // Add a fake delay to make the loading state visible.
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ if (url === '/the-beatles/albums') {
+ // Fail the first attempt to demonstrate the Error Boundary,
+ // then succeed on retry.
+ if (!retried) {
+ throw new Error('Example Error: Failed to fetch albums');
+ }
+ return [{
+ id: 13,
+ title: 'Let It Be',
+ year: 1970
+ }, {
+ id: 12,
+ title: 'Abbey Road',
+ year: 1969
+ }, {
+ id: 11,
+ title: 'Yellow Submarine',
+ year: 1969
+ }, {
+ id: 10,
+ title: 'The Beatles',
+ year: 1968
+ }];
+ }
+ throw new Error('Not implemented');
}
```
From d6aa25477d6a332ce5a645c41661180b9e4b3b59 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Fri, 22 May 2026 11:29:41 +0200
Subject: [PATCH 4/7] Address additional review feedback
---
src/content/reference/react/use.md | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index b352ca91be3..d32d3af5f16 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -438,7 +438,7 @@ export async function fetchAlbums() {
##### Promises passed to `use` must be cached {/*promises-must-cached*/}
-Promises created inside a component are recreated on every render. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component.
+Promises created inside a component are recreated on every render because [React doesn't preserve state for renders that suspended before mounting](/reference/react/Suspense#caveats). After each suspension, React retries rendering from scratch, so any Promise created during render is recreated. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component.
Common ways a Promise can be unintentionally recreated on every render:
@@ -1054,7 +1054,6 @@ root.render(
-
#### Should I resolve a Promise in a Server or Client Component? {/*resolve-promise-in-server-or-client-component*/}
@@ -1213,8 +1212,6 @@ async function getData(url) {
---
----
-
## Troubleshooting {/*troubleshooting*/}
### I'm getting an error: "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/}
From a5ee7423516eda2311dc3ba10e5e71b4f09b1b88 Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Fri, 22 May 2026 17:21:46 +0200
Subject: [PATCH 5/7] Split Pitfall and DeepDive, clarify recreation examples
---
src/content/reference/react/use.md | 38 +++++++++++++++++++++++++-----
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index d32d3af5f16..8b5f94ce5ac 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -438,17 +438,43 @@ export async function fetchAlbums() {
##### Promises passed to `use` must be cached {/*promises-must-cached*/}
-Promises created inside a component are recreated on every render because [React doesn't preserve state for renders that suspended before mounting](/reference/react/Suspense#caveats). After each suspension, React retries rendering from scratch, so any Promise created during render is recreated. This causes React to show the Suspense fallback repeatedly and prevents content from appearing. Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component.
+Promises created during render are recreated on every render, which causes React to show the Suspense fallback repeatedly and prevents content from appearing.
-Common ways a Promise can be unintentionally recreated on every render:
+```js
+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:
+
+```js
+// ✅ fetchData reads the Promise from a cache.
+const albums = use(fetchData('/albums'));
+```
+
+
+
+
+
+#### Why are Promises recreated on every render? {/*why-promises-recreated*/}
+
+[React doesn't preserve state for renders that suspended before mounting](/reference/react/Suspense#caveats). 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:
```js
function Albums() {
// 🔴 `fetch` creates a new Promise on every render.
const albums = use(fetch('/albums'));
- // 🔴 Calling an async function creates a new Promise on every render.
- const albums = use(getAlbums());
+ // 🔴 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.
@@ -458,13 +484,13 @@ function Albums() {
```
```js
-// ✅ fetchData reads the promise from a cache.
+// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));
```
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.
-
+
---
From 1e7b575aa6ee4b368ed5a74dc61dc707487315bd Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Fri, 22 May 2026 17:23:43 +0200
Subject: [PATCH 6/7] Reorder DeepDive: prose before correct example
---
src/content/reference/react/use.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index 8b5f94ce5ac..230333f0921 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -483,13 +483,13 @@ function Albums() {
}
```
+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.
+
```js
// ✅ fetchData reads the Promise from a cache.
const albums = use(fetchData('/albums'));
```
-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.
-
---
From f58c01956264543473db059461749f8de2a6b22e Mon Sep 17 00:00:00 2001
From: Aurora Scharff
Date: Sun, 24 May 2026 16:59:41 +0200
Subject: [PATCH 7/7] Add Reading a Promise from context section
---
src/content/reference/react/use.md | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md
index 230333f0921..3cc05532409 100644
--- a/src/content/reference/react/use.md
+++ b/src/content/reference/react/use.md
@@ -222,6 +222,33 @@ function Button({ show, children }) {
+### Reading a Promise from context {/*reading-a-promise-from-context*/}
+
+You can pass a Promise through context to share data without prop drilling. Pass the Promise as the context value, then call `use(context)` to read the Promise and `use(promise)` to read its resolved value:
+
+```js
+import { use } from 'react';
+import { UserContext } from './UserContext';
+
+function Profile() {
+ const userPromise = use(UserContext);
+ const user = use(userPromise);
+ return {user.name} ;
+}
+```
+
+Wrap the components that read the Promise in [``](/reference/react/Suspense) so only that subtree suspends while the Promise is pending. See [Usage (Promises)](#usage-promises) below for more on reading Promises with `use`.
+
+
+
+Passing a Promise through context is convenient, but comes with trade-offs. Prefer passing the Promise as a [prop](#reading-a-promise-with-use) from a [Server Component](/reference/rsc/server-components) when possible.
+
+* **Double unwrap.** Reading the value requires two calls: `use(context)` to get the Promise, then `use(promise)` to get its resolved value. The context value itself is not awaited.
+* **Revalidation scope.** Because the Promise is created in the component that provides it, revalidating the data requires re-rendering that component. Hoisting data into a component high in the tree means everything below it re-renders to update the data.
+* **One Promise per context.** Each piece of data needs its own context. Reaching for more contexts to share more data is usually a sign that the data should be fetched closer to where it's used.
+
+
+
---
## Usage (Promises) {/*usage-promises*/}