diff --git a/src/content/reference/react/index.md b/src/content/reference/react/index.md index 650a1c382..cec71ce8f 100644 --- a/src/content/reference/react/index.md +++ b/src/content/reference/react/index.md @@ -106,6 +106,24 @@ To prioritize rendering, use one of these Hooks: --- +## Resource Hooks {/*resource-hooks*/} + +*Resources* can be accessed by a component without having them as part of their state. For example, a component can read a message from a Promise or read styling information from a context. + +To read a value from a resource, use this Hook: + +- [`use`](/reference/react/use) lets you 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). + +```js +function MessageComponent({ messagePromise }) { + const message = use(messagePromise); + const theme = use(ThemeContext); + // ... +} +``` + +--- + ## Other Hooks {/*other-hooks*/} These Hooks are mostly useful to library authors and aren't commonly used in the application code. diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md new file mode 100644 index 000000000..8e1a8f213 --- /dev/null +++ b/src/content/reference/react/use.md @@ -0,0 +1,496 @@ +--- +title: use +canary: true +--- + + + +The `use` Hook is currently only available in React's canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). + + + + + +`use` is a React Hook that lets you 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). + +```js +const value = use(resource); +``` + + + + + +--- + +## Reference {/*reference*/} + +### `use(resource)` {/*use*/} + +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 +import { use } from 'react'; + +function MessageComponent({ messagePromise }) { + const message = use(messagePromise); + const theme = use(ThemeContext); + // ... +``` + +Unlike all other React Hooks, `use` can be called within loops and conditional statements like `if`. Like other React Hooks, the function that calls `use` must be a Component or Hook. + +When called with a Promise, the `use` Hook 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` Hook. 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). + +#### Returns {/*returns*/} + +The `use` Hook 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). + +#### Caveats {/*caveats*/} + +* The `use` Hook must be called inside a Component or a Hook. +* When fetching data in a [Server Component](/reference/react/use-server), 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/react/use-server) and passing them to [Client Components](/reference/react/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). + +--- + +## Usage {/*usage*/} + +### 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. + +```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. + +To pass context to a `Button`, wrap it or one of its parent components into the corresponding context provider. + +```js [[1, 3, "ThemeContext"], [2, 3, "\\"dark\\""], [1, 5, "ThemeContext"]] +function MyPage() { + return ( + +
+ + ); +} + +function Form() { + // ... renders buttons inside ... +} +``` + +It doesn't matter how many layers of components there are between the provider and the `Button`. When a `Button` *anywhere* inside of `Form` calls `use(ThemeContext)`, it will receive `"dark"` as the value. + +Unlike [`useContext`](/reference/react/useContext), `use` can be called in conditionals and loops like `if`. + +```js [[1, 2, "if"], [2, 3, "use"]] +function HorizontalRule({ show }) { + if (show) { + const theme = use(ThemeContext); + return
; + } + return false; +} +``` + +`use` is called from inside a `if` statement, allowing you to conditionally read values from a Context. + + + +Like `useContext`, `use(context)` always looks for the closest context provider *above* the component that calls it. It searches upwards and **does not** consider context providers in the component from which you're calling `use(context)`. + + + + + +```js +import { createContext, use } from 'react'; + +const ThemeContext = createContext(null); + +export default function MyApp() { + return ( + + + + ) +} + +function Form() { + return ( + + + + + ); +} + +function Panel({ title, children }) { + const theme = use(ThemeContext); + const className = 'panel-' + theme; + return ( +
+

{title}

+ {children} +
+ ) +} + +function Button({ show, children }) { + if (show) { + const theme = use(ThemeContext); + const className = 'button-' + theme; + return ( + + ); + } + return false +} +``` + +```css +.panel-light, +.panel-dark { + border: 1px solid black; + border-radius: 4px; + padding: 20px; +} +.panel-light { + color: #222; + background: #fff; +} + +.panel-dark { + color: #fff; + background: rgb(23, 32, 42); +} + +.button-light, +.button-dark { + border: 1px solid #777; + padding: 5px; + margin-right: 10px; + margin-top: 10px; +} + +.button-dark { + background: #222; + color: #fff; +} + +.button-light { + background: #fff; + color: #222; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-9377e1010-20230712", + "react-dom": "18.3.0-canary-9377e1010-20230712", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` + +
+ +### Streaming data from the server to the 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 [[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'; + +export default function App() { + const messagePromise = fetchMessage(); + return ( + waiting for message...

}> + +
+ ); +} +``` + +The Client Component then takes the Promise it received as a prop and passes it to the `use` Hook. 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"]] +// message.js +'use client'; + +import { use } from 'react'; + +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` Hook and the `Message` component will replace the Suspense fallback. + + + +```js message.js active +"use client"; + +import { use, Suspense } from "react"; + +function Message({ messagePromise }) { + const messageContent = use(messagePromise); + return

Here is the message: {messageContent}

; +} + +export function MessageContainer({ messagePromise }) { + return ( + ⌛Downloading message...

}> + +
+ ); +} +``` + +```js App.js hidden +import { useState } from "react"; +import { MessageContainer } from "./message.js"; + +function fetchMessage() { + return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️")); +} + +export default function App() { + const [messagePromise, setMessagePromise] = useState(null); + const [show, setShow] = useState(false); + function download() { + setMessagePromise(fetchMessage()); + setShow(true); + } + + if (show) { + return ; + } else { + return ; + } +} +``` + +```js index.js hidden +// TODO: update to import from stable +// react instead of canary once the `use` +// Hook is in a stable release of React +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( + + + +); +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-9377e1010-20230712", + "react-dom": "18.3.0-canary-9377e1010-20230712", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` +
+ + + +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*/} + +A Promise can be passed from a Server Component to a Client Component and resolved in the Client component with the `use` Hook. 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 +export default function App() { + const messageContent = await fetchMessage(); + return +} +``` + +But using `await` in a [Server Component](/reference/react/components#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. + + + +### 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 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 a error boundary {/*displaying-an-error-to-users-with-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` Hook in an error boundary. If the Promise passed to `use` is rejected the fallback for the error boundary will be displayed. + + + +```js 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...

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

Here is the message: {content}

; +} +``` + +```js App.js hidden +import { useState } from "react"; +import { MessageContainer } from "./message.js"; + +function fetchMessage() { + return new Promise((resolve, reject) => setTimeout(reject, 1000)); +} + +export default function App() { + const [messagePromise, setMessagePromise] = useState(null); + const [show, setShow] = useState(false); + function download() { + setMessagePromise(fetchMessage()); + setShow(true); + } + + if (show) { + return ; + } else { + return ; + } +} +``` + +```js index.js hidden +// TODO: update to import from stable +// react instead of canary once the `use` +// Hook is in a stable release of React +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( + + + +); +``` + +```json package.json hidden +{ + "dependencies": { + "react": "18.3.0-canary-9377e1010-20230712", + "react-dom": "18.3.0-canary-9377e1010-20230712", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` +
+ +#### 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'; + +export default function App() { + const messagePromise = new Promise((resolve, reject) => { + reject(); + }).catch(() => { + return "no new message found."; + }); + + return ( + waiting for message...

}> + +
+ ); +} +``` + +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. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### "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). + +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); + // ... +``` diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 3251873c1..627256937 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -10,6 +10,11 @@ "title": "Hooks", "path": "/reference/react", "routes": [ + { + "title": "use", + "path": "/reference/react/use", + "canary": true + }, { "title": "useCallback", "path": "/reference/react/useCallback"