From f6ff6b8fa9ba4c14737651cfff183b79e41e39a1 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 1 Sep 2022 11:44:52 -0400 Subject: [PATCH 1/2] [Beta] Initial API doc for Suspense This doesn't include everything but I think it's good enough for a start. I didn't add any interactive examples yet because I want to use the `use` API, so I'll wait until that's documented. --- beta/src/content/apis/react/Suspense.md | 121 ++++++++++++++++++++++-- beta/src/sidebarReference.json | 2 +- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/beta/src/content/apis/react/Suspense.md b/beta/src/content/apis/react/Suspense.md index 860b0abd4c4..65b21519a59 100644 --- a/beta/src/content/apis/react/Suspense.md +++ b/beta/src/content/apis/react/Suspense.md @@ -2,19 +2,126 @@ title: Suspense --- - + + +`Suspense` is a React Component that displays a fallback until its children have finished loading. -This section is incomplete, please see the old docs for [Suspense.](https://reactjs.org/docs/react-api.html#reactsuspense) - +```js +}> + + +``` + - +- [Usage](#usage) + - [Displaying a fallback while data is loading](#displaying-a-fallback-while-data-is-loading) + - [Revealing nested content as it loads](#revealing-nested-content-as-it-loads) + - [Lazy loading components with Suspense](#suspense-for-code-splitting) + - [Fetching data with Suspense](#suspense-for-data-fetching) +- [Reference](#reference) + - [Props](#props) +- [Troubleshooting](#troubleshooting) + - [How do I prevent the UI from being replaced by a fallback during an update?](#preventing-unwanted-fallbacks) + +--- + +## Usage {/*usage*/} + +### Displaying a fallback while data is loading {/*displaying-a-fallback-while-data-is-loading*/} + +You can wrap any part of your application with a Suspense component. If the data in its children hasn't loaded yet, React will switch to rendering the `fallback` prop instead. For example: + +```js [[1, 3, ""], [2, 4, ""]] +<> + + }> + + + +``` + +Suppose that `` takes longer to load than ``. Without a Suspense boundary, React wouldn't be able to show either component until both have loaded — `` would be blocked by ``. + +Because of the Suspense boundary, `` doesn't need to wait for ``. React renders `` in its place. Once `` finishes loading, it replaces `` with ``. + +Only Suspense-enabled data sources will activate a Suspense boundary's `fallback` prop. These data sources are said to *suspend* when the data needed to render has not yet loaded. Refer to the sections on [lazy loading](#suspense-for-code-splitting) and [data fetching](#suspense-for-data-fetching) for more information. + +Suspense does not detect when data is fetched inside an Effect or event handler. + +### Revealing nested content as it loads {/*revealing-nested-content-as-it-loads*/} + +When a component suspends, it activates the fallback of only the nearest parent Suspense boundary. This means you can nest multiple Suspense boundaries to create a loading sequence. Each Suspense boundary's fallback will be filled in as the next level of content becomes available. + +To illustrate, consider the following example: ```js -}>{...} +}> + + + }> + + + + ``` - +The sequence will be: + +- If `` hasn't loaded yet, `` is shown in place of the entire main content area. +- Once `` finishes loading, `` is replaced by the main content. +- If `` hasn't loaded yet, `` is shown in its place. +- Finally, once `` finishes loading, it replaces ``. + +### Lazy loading components with Suspense {/*suspense-for-code-splitting*/} + +The [`lazy`](/apis/react/lazy) API is powered by Suspense. When you render a component imported with `lazy`, it will suspend if it hasn't loaded yet. + +```js +import { Suspense, lazy } from 'react'; + +const MoreInfo = lazy(() => import('./MoreInfo')); + +function Details({showMore}) { + return ( + <> + + {showMore ? ( + }> + + + ) : null} + + ); +} +``` + +In this example, the code for `` won't be loaded until you attempt to render it. If `` hasn't loaded yet, `` will be shown in its place. + +### Fetching data with Suspense {/*suspense-for-data-fetching*/} + + +The requirements for implementing a Suspense-enabled data source are currently unstable and undocumented. An official API for integrating data sources with Suspense is in progress, and will be released soon in an upcoming minor. + + +General support for data fetching with Suspense without the use of a framework is not yet officially supported. However, you can use Suspense for data fetching in opinionated frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/), [Next.js](https://nextjs.org/docs/advanced-features/react-18), [Hydrogen](https://hydrogen.shopify.dev/), and [Remix](https://remix.run/). + +## Reference {/*reference*/} + +### Props {/*props*/} +* `children`: The actual UI you intend to render. If `children` suspends while rendering, the Suspense boundary will switch to rendering `fallback`. +* `fallback`: An alternate UI to render in place of the actual UI if it has not finished loading. Any valid React node is accepted, though in practice, a fallback is a lightweight placeholder view, such as a loading spinner or skeleton. Suspense will automatically switch to `fallback` when `children` suspends, and back to `children` when the data is ready. If `fallback` suspends while rendering, it will activate the next parent Suspense boundary. + +## Troubleshooting {/*troubleshooting*/} + +### How do I prevent the UI from being replaced by a fallback during an update? {/*preventing-unwanted-fallbacks*/} + +Replacing visible UI with a fallback creates a jarring user experience. This can happen when an update causes a component to suspend, and the nearest Suspense boundary is already showing content to the user. + +To prevent this from happening, the typical solution is to mark the update as non-urgent using [`startTransition`](/apis/react/startTransition). During a transition, React will wait until enough data has loaded to prevent an unwanted fallback from appearing. + +**React will only prevent unwanted fallbacks during non-urgent updates**. It will not delay a render if it's the result of an urgent update. You must opt in with an API like `startTransition` or [`useDeferredValue`](/apis/react/useDeferredValue). + +As a best practice, navigation-style updates should be automatically wrapped with `startTransition` by your routing framework, as well as core UI components like dropdowns or tab switchers. If your framework implements this pattern, most user code will not need to call `startTransition` explicitly. - diff --git a/beta/src/sidebarReference.json b/beta/src/sidebarReference.json index 9b504af0e05..ce4a23ff23e 100644 --- a/beta/src/sidebarReference.json +++ b/beta/src/sidebarReference.json @@ -86,7 +86,7 @@ { "title": "Suspense", "path": "/apis/react/Suspense", - "wip": true + "wip": false }, { "title": "useCallback", From cb314a85878b0e6d7e179a3ea3881f5962c27995 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 15 Sep 2022 19:36:20 +0100 Subject: [PATCH 2/2] edits --- beta/src/content/apis/react/Suspense.md | 217 +++++++++++++++++++----- beta/src/sidebarReference.json | 3 +- 2 files changed, 175 insertions(+), 45 deletions(-) diff --git a/beta/src/content/apis/react/Suspense.md b/beta/src/content/apis/react/Suspense.md index 65b21519a59..2cf96b724c1 100644 --- a/beta/src/content/apis/react/Suspense.md +++ b/beta/src/content/apis/react/Suspense.md @@ -4,34 +4,26 @@ title: Suspense -`Suspense` is a React Component that displays a fallback until its children have finished loading. +`Suspense` is a React component that displays a fallback until its children have finished loading. ```js -}> - +}> + ``` -- [Usage](#usage) - - [Displaying a fallback while data is loading](#displaying-a-fallback-while-data-is-loading) - - [Revealing nested content as it loads](#revealing-nested-content-as-it-loads) - - [Lazy loading components with Suspense](#suspense-for-code-splitting) - - [Fetching data with Suspense](#suspense-for-data-fetching) -- [Reference](#reference) - - [Props](#props) -- [Troubleshooting](#troubleshooting) - - [How do I prevent the UI from being replaced by a fallback during an update?](#preventing-unwanted-fallbacks) + --- ## Usage {/*usage*/} -### Displaying a fallback while data is loading {/*displaying-a-fallback-while-data-is-loading*/} +### Displaying a fallback while something is loading {/*displaying-a-fallback-while-something-is-loading*/} -You can wrap any part of your application with a Suspense component. If the data in its children hasn't loaded yet, React will switch to rendering the `fallback` prop instead. For example: +You can wrap any part of your application with a Suspense component. If either data or code in its children hasn't loaded yet, React will switch to rendering the `fallback` prop instead. For example: ```js [[1, 3, ""], [2, 4, ""]] <> @@ -42,21 +34,48 @@ You can wrap any part of your application with a Suspense component. If the data ``` -Suppose that `` takes longer to load than ``. Without a Suspense boundary, React wouldn't be able to show either component until both have loaded — `` would be blocked by ``. +Suppose that `Comments` takes longer to load than `Post`. Without a Suspense boundary, React wouldn't be able to show either component until both have loaded — `Post` would be blocked by `Comments`. -Because of the Suspense boundary, `` doesn't need to wait for ``. React renders `` in its place. Once `` finishes loading, it replaces `` with ``. +Because of the Suspense boundary, `Post` doesn't need to wait for `Comments`. React renders `LoadingSpinner` in its place. Once `Comments` finishes loading, React replaces `LoadingSpinner` with `Comments`. -Only Suspense-enabled data sources will activate a Suspense boundary's `fallback` prop. These data sources are said to *suspend* when the data needed to render has not yet loaded. Refer to the sections on [lazy loading](#suspense-for-code-splitting) and [data fetching](#suspense-for-data-fetching) for more information. +Suspense will never show unintentional "holes" in your content. For example, if `PhotoAlbums` has loaded but `Notes` have not, with the structure below, it will still show a `LoadingIndicator` instead of the entire `Grid`: + +```js {4-7} +<> + + }> + + + + + + +``` + +To reveal nested content as it loads, you need to [add more Suspense boundaries.](#revealing-nested-content-as-it-loads) + + + +**Only Suspense-enabled data sources will activate a Suspense boundary.** These data sources are said to *suspend* when the data needed to render has not yet loaded. Currently, Suspense is only supported for: + +- [Lazy-loading components](#suspense-for-code-splitting) +- Data fetching with opinionated frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/), [Next.js](https://nextjs.org/docs/advanced-features/react-18), [Hydrogen](https://hydrogen.shopify.dev/), and [Remix](https://remix.run/) + +Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. Suspense does not detect when data is fetched inside an Effect or event handler. + + +--- + ### Revealing nested content as it loads {/*revealing-nested-content-as-it-loads*/} When a component suspends, it activates the fallback of only the nearest parent Suspense boundary. This means you can nest multiple Suspense boundaries to create a loading sequence. Each Suspense boundary's fallback will be filled in as the next level of content becomes available. To illustrate, consider the following example: -```js +```js {1,4} }> @@ -69,49 +88,151 @@ To illustrate, consider the following example: The sequence will be: -- If `` hasn't loaded yet, `` is shown in place of the entire main content area. -- Once `` finishes loading, `` is replaced by the main content. -- If `` hasn't loaded yet, `` is shown in its place. -- Finally, once `` finishes loading, it replaces ``. +- If `Post` hasn't loaded yet, `BigSpinner` is shown in place of the entire main content area. +- Once `Post` finishes loading, `BigSpinner` is replaced by the main content. +- If `Comments` hasn't loaded yet, `CommentsGlimmer` is shown in its place. +- Finally, once `Comments` finishes loading, it replaces `CommentsGlimmer`. + +--- ### Lazy loading components with Suspense {/*suspense-for-code-splitting*/} -The [`lazy`](/apis/react/lazy) API is powered by Suspense. When you render a component imported with `lazy`, it will suspend if it hasn't loaded yet. +The [`lazy`](/apis/react/lazy) API is powered by Suspense. When you render a component imported with `lazy`, it will suspend if it hasn't loaded yet. This allows you to display a loading indicator while your component's code is loading. -```js -import { Suspense, lazy } from 'react'; +```js {3,12-14} +import { lazy, Suspense, useState } from 'react'; + +const MarkdownPreview = lazy(() => import('./MarkdownPreview.js')); + +function MarkdownEditor() { + const [showPreview, setShowPreview] = useState(false); + // ... + return ( + <> + ... + {showPreview && ( + }> + + + )} + + ); +} +``` + +In this example, the code for `MarkdownPreview` won't be loaded until you attempt to render it. If `MarkdownPreview` hasn't loaded yet, `Loading` will be shown in its place. Try ticking the checkbox: + + + +```js App.js +import { useState, Suspense, lazy } from 'react'; +import Loading from './Loading.js'; -const MoreInfo = lazy(() => import('./MoreInfo')); +const MarkdownPreview = lazy(() => delayForDemo(import('./MarkdownPreview.js'))); -function Details({showMore}) { +export default function MarkdownEditor() { + const [showPreview, setShowPreview] = useState(false); + const [markdown, setMarkdown] = useState('Hello, **world**!'); return ( <> - - {showMore ? ( - }> - +