diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index b521970b764..dbf3180ae72 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -4,10 +4,10 @@ title: -`` lets you hide and restore the UI and internal state of its children. +`` lets you hide and reveal part of the UI while preserving its state. ```js - + ``` @@ -22,215 +22,124 @@ title: ### `` {/*activity*/} -You can use Activity to hide part of your application: +Wrap part of the component tree in `` to control whether it is visible: -```js [[1, 1, "\\"hidden\\""], [2, 2, ""], [3, 1, "\\"visible\\""]] - +```js +import { Activity } from 'react'; + + ``` -When an Activity boundary is hidden, React will visually hide its children using the `display: "none"` CSS property. It will also destroy their Effects, cleaning up any active subscriptions. +[See more examples below.](#usage) -While hidden, children still re-render in response to new props, albeit at a lower priority than the rest of the content. +#### Modes {/*modes*/} -When the boundary becomes visible again, React will reveal the children with their previous state restored, and re-create their Effects. +An Activity boundary supports two modes: -In this way, Activity can be thought of as a mechanism for rendering "background activity". Rather than completely discarding content that's likely to become visible again, you can use Activity to maintain and restore that content's UI and internal state, while ensuring that your hidden content has no unwanted side effects. +- In `visible` mode, React renders the children, attaches their refs, and runs the setup functions for their `useEffect` and `useLayoutEffect` calls. +- In `hidden` mode, React hides the children, detaches their refs, and runs the cleanup functions for their `useEffect` and `useLayoutEffect` calls. React preserves their state and renders updates at a lower priority than updates to visible content. -[See more examples below.](#usage) +When a hidden Activity boundary becomes visible, React reveals its children with their previous state and runs their Effect setup functions again. + +In React DOM, hiding an Activity boundary applies `display: none` to the nearest DOM elements inside the boundary. React preserves those elements while the boundary remains mounted. + +Insertion Effects created with [`useInsertionEffect`](/reference/react/useInsertionEffect) remain connected while an Activity boundary is hidden because styles may still be needed by the preserved DOM. #### Props {/*props*/} -* `children`: The UI you intend to show and hide. -* `mode`: A string value of either `'visible'` or `'hidden'`. If omitted, defaults to `'visible'`. +* `children`: The UI rendered by the Activity boundary. `children` can be any [React node](/reference/react/isValidElement#react-elements-vs-react-nodes). +* **optional** `mode`: Either `'visible'` or `'hidden'`. Defaults to `'visible'`. See [Modes](#modes) for the behavior of each value. +* **optional** `name`: A string that identifies the Activity boundary in React Developer Tools. #### Caveats {/*caveats*/} -- If an Activity is rendered inside of a [ViewTransition](/reference/react/ViewTransition), and it becomes visible as a result of an update caused by [startTransition](/reference/react/startTransition), it will activate the ViewTransition's `enter` animation. If it becomes hidden, it will activate its `exit` animation. -- A *hidden* Activity that just renders text will not render anything rather than rendering hidden text, because there’s no corresponding DOM element to apply visibility changes to. For example, `` will not produce any output in the DOM for `const ComponentThatJustReturnsText = () => "Hello, World!"`. `` will render visible text. +- Browser behavior associated with preserved DOM nodes can continue while the boundary is hidden. For example, audio and video can continue playing. Use an Effect cleanup function to stop this behavior. [See an example below.](#my-hidden-component-keeps-playing-audio-or-video) +- React omits text-only output while an Activity boundary is hidden because a text node cannot receive `display: none`. The text appears when the boundary becomes visible. +- If an Activity boundary is inside [``](/reference/react/ViewTransition), changing it from hidden to visible as part of an update started with [`startTransition`](/reference/react/startTransition) activates the `enter` animation. Changing it from visible to hidden as part of that update activates the `exit` animation. --- ## Usage {/*usage*/} -### Restoring the state of hidden components {/*restoring-the-state-of-hidden-components*/} - -In React, when you want to conditionally show or hide a component, you typically mount or unmount it based on that condition: - -```jsx -{isShowingSidebar && ( - -)} -``` - -But unmounting a component destroys its internal state, which is not always what you want. - -When you hide a component using an Activity boundary instead, React will "save" its state for later: - -```jsx - - - -``` - -This makes it possible to hide and then later restore components in the state they were previously in. - -The following example has a sidebar with an expandable section. You can 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, and then toggling the sidebar closed then open: - - - -```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'; +Activity is useful when part of the UI may become hidden and visible again. Unlike conditional rendering, hiding an Activity boundary preserves both React state and the DOM state of its children. Unlike hiding content only with CSS, Activity also cleans up the children's Effects and deprioritizes their updates while they are hidden. -export default function Sidebar() { - const [isExpanded, setIsExpanded] = useState(false) +Use an Activity boundary when hidden content is likely to become visible again, such as a tab the user may revisit or a panel that can prepare data in the background. A hidden boundary retains its state and DOM nodes, so it continues using memory. If the content is unlikely to become visible again, conditionally rendering it may be preferable because unmounting allows React and the browser to release its resources. - return ( - - ); -} -``` +Conditional rendering mounts or unmounts a component as its condition changes: -```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; -} +```js +{isShowingSidebar && } ``` -
- -The Overview section always starts out collapsed. 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. +Unmounting `` destroys its internal state. As a result, its state resets each time it is shown. -Let's replace the conditional rendering of our sidebar with an Activity boundary: - -```jsx {7,9} -// Before -{isShowingSidebar && ( - -)} +To preserve the state between hides, keep `` mounted inside an Activity boundary and change the boundary's `mode`: -// After +```js ``` -and check out the new behavior: +When the boundary becomes visible again, `` resumes with its previous state. + +The following example has a sidebar with an expandable Overview section and a button that hides and shows the sidebar. To verify that its state is preserved, expand the Overview section, hide the sidebar, and then show it again. The Overview section remains expanded. ```js src/App.js active import { 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 +```js src/Sidebar.js hidden import { useState } from 'react'; export default function Sidebar() { - const [isExpanded, setIsExpanded] = useState(false) + const [isExpanded, setIsExpanded] = useState(false); return ( -