diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index dc43fa2289c..3890c50b551 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -18,53 +18,80 @@ 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`. -```jsx +```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*/} -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. +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. -[See more examples below.](#usage) +```js +import { use } from 'react'; -#### Parameters {/*parameters*/} +function MessageComponent({ messagePromise }) { + const message = use(messagePromise); + // ... +``` -* `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). +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. -#### Returns {/*returns*/} +[See more examples below.](#usage-promises) -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). +#### Parameters {/*promise-parameters*/} -#### Caveats {/*caveats*/} +* `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. -* The `use` API must be called inside a Component or a Hook. -* 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). +#### Returns {/*promise-returns*/} + +The resolved value of the Promise. + +#### 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) 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*/} -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'; function Button() { const theme = use(ThemeContext); - // ... + // ... ``` `use` returns the context value for the context you passed. To determine the context value, React searches the component tree and finds **the closest context provider above** for that particular context. @@ -194,11 +221,734 @@ function Button({ show, children }) { -### Streaming data from the server to the client {/*streaming-data-from-server-to-client*/} +--- + +## 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. + +```js [[1, 4, "use(albumsPromise)"]] +import { use } from 'react'; + +function Albums({ albumsPromise }) { + const albums = use(albumsPromise); + return ( + + ); +} +``` + +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 ( + + ); +} + +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. -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 src/App.js active +import { useState, useEffect } from 'react'; +import { fetchAlbums } from './data.js'; -```js [[1, 4, "App"], [2, 2, "Message"], [3, 7, "Suspense"], [4, 8, "messagePromise", 30], [4, 5, "messagePromise"]] +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 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. + +```js +function Albums() { + // 🔴 fetch creates a new Promise on every render. + const albums = use(fetch('/albums')); + // ... +} +``` + +```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. + + + +--- + +### Caching Promises for Client Components {/*caching-promises-for-client-components*/} + +Promises passed to `use` in Client Components must be cached so the same Promise instance is reused across re-renders. If a new Promise is created directly in render, React will display the Suspense fallback on every re-render. + +```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 ( + <> + +

Render count: {count}

+ Loading...

}> + +
+ + ); +} + +function Albums() { + const albums = use(fetchData('/albums')); + return ( + + ); +} +``` + +```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 + }]; +} +``` + +
+ + + +#### 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. + +```js +let cache = new Map(); + +function fetchData(url) { + if (!cache.has(url)) { + const promise = getData(url); + promise.status = 'pending'; + promise.then( + value => { + promise.status = 'fulfilled'; + promise.value = value; + }, + reason => { + promise.status = 'rejected'; + promise.reason = reason; + }, + ); + cache.set(url, promise); + } + return cache.get(url); +} +``` + +This is primarily useful for library authors building Suspense-compatible data layers. React will set the `status` field itself on Promises that don't have it, but setting it yourself avoids an extra render when the data is already available. + +This cache pattern is the foundation for [re-fetching data](#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). + + + + + +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); +``` + + + +--- + +### Re-fetching data in Client Components {/*re-fetching-data-in-client-components*/} + +To refresh data at the same URL (for example, with a "Refresh" button), invalidate the cache entry and start a new fetch inside a [`startTransition`](/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')); + }); + } + // ... +} +``` + +`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. + + + +```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 [albumsPromise, setAlbumsPromise] = useState( + () => fetchData('/the-beatles/albums') + ); + const [isPending, startTransition] = useTransition(); + + function handleRefresh() { + startTransition(() => { + setAlbumsPromise(refetchData('/the-beatles/albums')); + }); + } + + return ( + <> + +
+ }> + + +
+ + ); +} + +function Albums({ albumsPromise }) { + const albums = use(albumsPromise); + return ( + + ); +} + +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); +} + +export function refetchData(url) { + cache.delete(url); + return fetchData(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 + + ))} + + }> + + + + ); +} + +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 ( + + ); +} +``` + +```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; } +``` + + + +--- + +### 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. + +```js import { fetchMessage } from './lib.js'; import { Message } from './message.js'; @@ -212,9 +962,9 @@ export default function App() { } ``` -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 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. -```js [[2, 6, "Message"], [4, 6, "messagePromise"], [4, 7, "messagePromise"], [5, 7, "use"]] +```js // message.js 'use client'; @@ -225,7 +975,7 @@ export function Message({ 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. +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. @@ -292,12 +1042,6 @@ root.render( - - -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. - - - @@ -316,85 +1060,76 @@ 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. -```js src/message.js active -"use client"; - -import { use, Suspense } from "react"; +```js src/App.js active +import { use, Suspense, useState, useTransition } from "react"; import { ErrorBoundary } from "react-error-boundary"; +import { fetchData } from "./data.js"; + +export default function App() { + const [albumsPromise, setAlbumsPromise] = useState( + () => fetchData('/the-beatles/albums') + ); + const [isPending, startTransition] = useTransition(); + + function handleRefresh() { + startTransition(() => { + setAlbumsPromise(fetchData('/the-beatles/albums')); + }); + } -export function MessageContainer({ messagePromise }) { return ( - ⚠️Something went wrong

}> - ⌛Downloading message...

}> - -
-
+ <> + + ⚠️Something went wrong

}> + Loading...

}> + +
+
+ ); } -function Message({ messagePromise }) { - const content = use(messagePromise); - return

Here is the message: {content}

; +function Albums({ albumsPromise }) { + const albums = use(albumsPromise); + return ( + + ); } ``` -```js src/App.js hidden -import { useState } from "react"; -import { MessageContainer } from "./message.js"; - -function fetchMessage() { - return new Promise((resolve, reject) => setTimeout(reject, 1000)); -} +```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. -export default function App() { - const [messagePromise, setMessagePromise] = useState(null); - const [show, setShow] = useState(false); - function download() { - setMessagePromise(fetchMessage()); - setShow(true); - } - - if (show) { - return ; +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 { - return ; + 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'; - -const root = createRoot(document.getElementById('root')); -root.render( - - - -); +export function fetchData(url) { + return getData(url); +} ``` ```json package.json hidden @@ -410,53 +1145,64 @@ root.render( ```
-#### 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. +--- -```js [[1, 6, "catch"],[2, 7, "return"]] -import { Message } from './message.js'; +## Troubleshooting {/*troubleshooting*/} -export default function App() { - const messagePromise = new Promise((resolve, reject) => { - reject(); - }).catch(() => { - return "no new message found."; - }); +### I'm getting an error: "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} - return ( - waiting for message...

}> - -
- ); -} +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. + +```jsx +function Albums({ albumsPromise }) { + try { + // ❌ Don't wrap `use` in try-catch + const albums = use(albumsPromise); + } catch (e) { + return

Error

; + } + // ... ``` -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. +Instead, wrap the component in an Error Boundary: ---- +```jsx +function Albums({ albumsPromise }) { + // âś… Call `use` without try-catch + const albums = use(albumsPromise); + // ... +``` -## Troubleshooting {/*troubleshooting*/} +```jsx +// âś… Use an Error Boundary to handle errors +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). +### I'm getting a warning: "A component was suspended by an uncached promise" {/*uncached-promise-error*/} -If you are calling `use` outside a React Component or Hook function, move the `use` call to a React Component or Hook function. +The Promise passed to `use` is not cached, so React cannot reuse it across re-renders. -```jsx -function MessageComponent({messagePromise}) { - function download() { - // ❌ the function calling `use` is not a Component or Hook - const message = use(messagePromise); - // ... +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(fetch('/albums')); + // ... +} ``` -Instead, call `use` outside any component closures, where the function that calls `use` is a Component or Hook. +To fix this, cache the Promise so the same instance is reused: -```jsx -function MessageComponent({messagePromise}) { - // ✅ `use` is being called from a component. - const message = use(messagePromise); - // ... +```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.