From 7a8454fc3520b9662bf51ae5797f4977f0cf884c Mon Sep 17 00:00:00 2001 From: Sam Selikoff Date: Thu, 17 Jul 2025 18:29:25 -0400 Subject: [PATCH 01/15] Updates to Activity docs --- src/content/reference/react/Activity.md | 1472 ++++++++++++----------- 1 file changed, 787 insertions(+), 685 deletions(-) diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index 8b103938e50..310ab264040 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -19,11 +19,11 @@ Experimental versions of React may contain bugs. Don't use them in production. -`` lets you hide and show part of the UI. +`` lets you hide and show its children without resetting their state. ```js - + ``` @@ -38,28 +38,14 @@ Experimental versions of React may contain bugs. Don't use them in production. ### `` {/*activity*/} -Wrap a part of the UI in `` to manage its visibility state: - -```js -import {unstable_Activity as Activity} from 'react'; - - - - -``` - -When "hidden", the `children` of `` are not visible on the page. If a new `` mounts as "hidden" then it pre-renders the content at lower priority without blocking the visible content on the page, but it does not mount by creating Effects. When a "visible" Activity switches to "hidden" it conceptually unmounts by destroying all the Effects, but saves its state. This allows fast switching between "visible" and "hidden" states without recreating the state for a "hidden" Activity. - -In the future, "hidden" Activities may automatically destroy state based on resources like memory. - #### Props {/*props*/} -* `children`: The actual UI you intend to render. +* `children`: The UI you intend to show and hide. * **optional** `mode`: Either "visible" or "hidden". Defaults to "visible". When "hidden", updates to the children are deferred to lower priority. The component will not create Effects until the Activity is switched to "visible". If a "visible" Activity switches to "hidden", the Effects will be destroyed. #### Caveats {/*caveats*/} -- While hidden, the `children` of `` are hidden on the page. +- While hidden, the `children` of `` are visually hidden on the page. - `` will unmount all Effects when switching from "visible" to "hidden" without destroying React or DOM state. This means Effects that are expected to run only once on mount will run again when switching from "hidden" to "visible". Conceptually, "hidden" Activities are unmounted, but they are not destroyed either. We recommend using [``](/reference/react/StrictMode) to catch any unexpected side-effects from this behavior. - When used with ``, hidden activities that reveal in a transition will activate an "enter" animation. Visible Activities hidden in a transition will activate an "exit" animation. - Parts of the UI wrapped in `` are not included in the SSR response. @@ -69,184 +55,554 @@ In the future, "hidden" Activities may automatically destroy state based on reso ## Usage {/*usage*/} -### Pre-render part of the UI {/*pre-render-part-of-the-ui*/} +### Hiding content without resetting React state {/*hiding-content-without-resetting-react-state*/} -You can pre-render part of the UI using ``: +You can use Activity to hide part of your application without resetting its local React state: -```js - - +```js [[1, 1, "\\"hidden\\""], [2, 2, ""], [3, 1, "\\"visible\\""]] + + ``` -When an Activity is rendered with `mode="hidden"`, the `children` are not visible on the page, but are rendered at lower priority than the visible content on the page. +When an Activity boundary becomes hidden, React will _visually_ hide its children without destroying any of its local component state. -When the `mode` later switches to "visible", the pre-rendered children will mount and become visible. This can be used to prepare parts of the UI the user is likely to interact with next to reduce loading times. +If the boundary becomes visible again, React will reveal the content with the same state values from before it was hidden. -In the following example from [`useTransition`](/reference/react/useTransition#preventing-unwanted-loading-indicators), the `PostsTab` component fetches some data using `use`. When you click the β€œPosts” tab, the `PostsTab` component suspends, causing the button loading state to appear: +--- + +The following example has a sidebar with an expandable section – press "Overview" to reveal the three subitems below it. The main app area also has a button that hides and shows the sidebar. + +Try expanding the Overview section, hiding the sidebar, and then showing the sidebar again: -```js -import { Suspense, useState } from 'react'; +```js src/App.js active +import { useState } from 'react'; +import Sidebar from './Sidebar.js'; + +export default function App() { + const [isShowingSidebar, setIsShowingSidebar] = useState(true); + + return ( + <> + {isShowingSidebar && ( + + )} + +
+ +

Main content

+
+ + ); +} +``` + +```js src/Sidebar.js +import { useState } from 'react'; + +export default function Sidebar() { + const [isExpanded, setIsExpanded] = useState(false) + + return ( + + ); +} +``` + +```css +body { height: 275px; margin: 0; } +#root { + display: flex; + gap: 10px; + height: 100%; +} +nav { + padding: 10px; + background: #eee; + font-size: 14px; + height: 100%; +} +main { + padding: 10px; +} +p { + margin: 0; +} +h1 { + margin-top: 10px; +} +.indicator { + margin-left: 4px; + display: inline-block; + rotate: 90deg; +} +.indicator.down { + rotate: 180deg; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +
+ +The Overview section starts out collapsed again! Because we unmount the sidebar when `isShowingSidebar` flips to `false`, all its internal state is lost. + +This is a perfect use case for Activity. We can preserve the internal state of our sidebar, even when visually hiding it. + +Let's replace the conditional rendering of our sidebar with an Activity boundary: + +```jsx {7,9} +// Before +{isShowingSidebar && ( + +)} + +// After + + + +``` + +and check out the new behavior: + + + +```js src/App.js active +import { unstable_Activity as Activity, useState } from 'react'; +import Sidebar from './Sidebar.js'; + +export default function App() { + const [isShowingSidebar, setIsShowingSidebar] = useState(true); + + return ( + <> + + + + +
+ +

Main content

+
+ + ); +} +``` + +```js src/Sidebar.js +import { useState } from 'react'; + +export default function Sidebar() { + const [isExpanded, setIsExpanded] = useState(false) + + return ( + + ); +} +``` + +```css +body { height: 275px; margin: 0; } +#root { + display: flex; + gap: 10px; + height: 100%; +} +nav { + padding: 10px; + background: #eee; + font-size: 14px; + height: 100%; +} +main { + padding: 10px; +} +p { + margin: 0; +} +h1 { + margin-top: 10px; +} +.indicator { + margin-left: 4px; + display: inline-block; + rotate: 90deg; +} +.indicator.down { + rotate: 180deg; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "experimental", + "react-dom": "experimental", + "react-scripts": "latest", + "toastify-js": "1.12.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} +``` + +
+ +Our sidebar's internal state is now preserved, and we didn't have to change anything about its implementation. + +--- + +### Hiding content without resetting the DOM {/*hiding-content-without-resetting-the-dom*/} + +Activity boundaries also preserve their children's DOM when hidden, making them great for preserving ephemeral state in parts of the UI the user is likely to interact with again. + +In this example, the Contact tab has a `