From d03513f4b22610b69181c23d71d2d059ab5d3758 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 25 Mar 2025 16:08:33 -0400 Subject: [PATCH 01/74] init --- src/content/reference/react/ViewTransition.md | 354 ++++++++++++++++++ src/sidebarReference.json | 5 + 2 files changed, 359 insertions(+) create mode 100644 src/content/reference/react/ViewTransition.md diff --git a/src/content/reference/react/ViewTransition.md b/src/content/reference/react/ViewTransition.md new file mode 100644 index 00000000000..f761aa6f696 --- /dev/null +++ b/src/content/reference/react/ViewTransition.md @@ -0,0 +1,354 @@ +--- +title: +canary: true +--- + + + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + +This API is only available inside React Server Components. + + + + + +`` lets you animate elements that update inside a Transition. + + +```js + +
...
+
+``` + +
+ + + +--- + +## Reference {/*reference*/} + +### `` {/*viewtransition*/} + +Wrap elements in `` to animate them when they update inside a [Transition](/reference/react/useTransition). Inside a Transition, React uses [heuristics](#heuristics) to determine if a `class` for the View Transition should be added to an element, and adds them for you automatically. The elements are then updated inside [`document.startViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) to trigger the animations. + + + + +#### How does `` work? {/*how-does-viewtransition-work*/} + +Under the hood, React applies view-transition-name to inline styles of the nearest DOM node nested inside the `` component. If there are multiple siblings DOM nodes like `
` then React adds a suffix to the name to make each unique but conceptually they're part of the same one. React doesn't apply these eagerly but only at the time that boundary should participate in an animation. + +React automatically calls `startViewTransition` itself behind the scenes so you should never do that yourself. In fact, if you have something else on the page running a ViewTransition React will interrupt it. So it's recommended that you use React itself to coordinate these. If you had other ways of trigger ViewTransitions in the past, we recommend that you migrate to the built-in way. + +If there are other React ViewTransitions already running then React will wait for them to finish before starting the next one. However, importantly if there are multiple updates happening while the first one is running, those will all be batched into one. If you start A->B. Then in the meantime you get an update to go to C and then D. When the first A->B animation finishes the next one will animate from B->D. + +The `getSnapshotBeforeUpdate` life-cycle will be called before `startViewTransition` and some `view-transition-name` will update at the same time. + +Then React calls `startViewTransition`. Inside the `updateCallback`, React will: + +- Apply its mutations to the DOM and invoke useInsertionEffects. +- Wait for fonts to load. +- Call componentDidMount, componentDidUpdate, useLayoutEffect and refs. +- Wait for any pending Navigation to finish. +- Then React will measure any changes to the layout to see which boundaries will need to animation. + +After the ready Promise of the `startViewTransition` is resolved, React will then revert the `view-transition-name`. Then React will invoke the `onEnter`, `onExit`, `onUpdate` and `onShare` callbacks to allow for manual programmatic control over the Animations. This will be after the built-in default ones have already been computed. + +If a `flushSync` happens to get in the middle of this sequence, then React will skip the Transition since it relies on being able to complete synchronously. + +After the finished Promise of the `startViewTransition` is resolved, React will then invoke `useEffect`. This prevents those from interfering with the performance of the Animation. However, this is not a guarantee because if another `setState` happens while the Animation is running it'll still have to invoke the `useEffect` earlier to preserve the sequential guarantees. + + + +#### Props {/*props*/} + +* **optional** `default`: A string or object. The [View Transition Class](#view-transition-class) to apply when no other matching trigger is found. +* **optional** `enter`: A string or object. The [View Transition Class](#view-transition-class) to apply when enter is activated. +* **optional** `exit`: A string or object. The [View Transition Class](#view-transition-class) to apply when exit is activated. +* **optional** `update`: A string or object. The [View Transition Class](#view-transition-class) to apply when an update is activated. +* **optional** `share`: A string or object. The [View Transition Class](#view-transition-class) to apply when a shared element is activated. +* **optional** `name`: A string or object. The name of the View Transition. If not provided, React will use a unique name for each View Transition. + +#### Events {/*events*/} + +* **optional** `onEnter`: A function. React calls `onEnter` after the View Transition animates an enter transition. +* **optional** `onExit`: A function. React calls `onExit` after the View Transition animates an exit transition. +* **optional** `onShare`: A function. React calls `onShare` after the View Transition animates a shared element transition. +* **optional** `onUpdate`: A function. React calls `onUpdate` after the View Transition animates an update transition. + +### View Transition Class {/*view-transition-classes*/} + +The View Transition Class is the CSS class name(s) applied by React during the transition when the ViewTransition activates. It can be a string or an object. +- `string`: the `class` added on the child elements when activated. If `'none'` is provided, no class will be added. +- `object`: the class added on the child elements will be the key matching View Transition type added with `addTransitionType`. The object can also specify a `default` to use if no matching type is found. + +The value `'none'` can be used to prevent a View Transition from activating for a specific trigger. + +#### Caveats {/*caveats*/} + +- By default, `setState` updates immediately and does not activate ``, only updates wrapped in a [Transition](/reference/react/useTransition). You can also use [``](/reference/react/Suspense) to opt-in to a Transition to [reveal content](/link-to-suspense-below). +- `` creates an image that can be moved around, scaled and cross-faded. Unlike Layout Animations you may have seen in React Native or Motion, this means that not every individual Element inside of it animates its position. This can lead to better performance and a more continuous feeling, smooth, animation compared to animating every individual piece. However, it can also lose continuity in things that should be moving by themselves. So you might have to add more `` boundaries manually as a result. +- Many users may prefer not having animations on the page. React doesn't automatically disable animations for this case. We recommend that using the `@media (prefers-reduced-motion)` media query to disable animations or tone them down based on user preference. In the future, CSS libraries may have this built-in to their presets. +- Currently, `` only works in the DOM. We're working on adding support for React Native and other platforms. + +### Heuristics {/*heuristics*/} + +Unlike if you manually add [`view-transition-name`](https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name) in the DOM, not all React `` boundaries will activate each time something animates. Instead, React will use a set of built-in heuristic to determine which needs to participate in the current animation. + +There are currently four types of triggers: + +- `enter`: If a ViewTransition itself gets inserted in this Transition, then this will activate. +- `exit`: If a ViewTransition itself gets deleted in this Transition, then this will activate. +- `update`: If a ViewTransition has any DOM mutations inside it that React is doing (such as a prop changing) or if the ViewTransition boundary itself changes size or position due to an immediate sibling. If there are nested ViewTransition then the mutation applies to them and not the parent. +- `share`: If a named ViewTransition is inside a deleted subtree and another named ViewTransition with the same name is part of an inserted subtree in the same Transition, they form a Shared Element Transition and it animates from the deleted one to the inserted one. + +- You can use a different animation for each kind of trigger (see [Styling View Transitions](#styling-view-transitions)). + +### Styling View Transitions {/*styling-view-transitions*/} + +In many early examples of View Transitions around the web you'll have seen using a [`view-transition-name`](https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name) and then style it using `::view-transition-...(my-name)` selectors. We don't recommend that for styling. Instead, we normally recommend using a View Transition Class instead. + + +```js [[1, 1, "default"], [2, 1, "my-class"]] + +``` + +When the ViewTransition activates, it will add the default class name my-class. Then you can refer to this class using [view transition pseudo selectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API#pseudo-elements) to build reusable animations: + +```css +::view-transition-group(.my-class) { + +} +::view-transition-old(.my-class) { + +} +::view-transition-new(.my-class) { + +} +``` + +We're excited to see many more CSS libraries adding built-in animations using View Transition Classes to make this easier to use. + +--- + + +## Usage {/*usage*/} + +### Animating an element on enter/exit {/*animating-an-element-on-enter*/} + +Enter/Exit Transitions trigger when a `` is added or removed by a component in a transition: + +```js [[1, 10, "startTransition"], [2, 2, ""], [2, 2, ""]] +function Child() { + return
Hello
; +} + +function Parent() { + const [show, setShow] = useState(false); + return ( + <> + + + {show && } + + ); +} +``` + +When `setShow` is called, `show` switched to `true` and the `Child` component is rendered. Since `setShow` is called inside startTransition, and `Child` renders a ViewTransition before any other DOM nodes, an `enter` animation is triggered. + +When `show` is switched back to `false`, an `exit` animation is triggered. + + + +`` only activates if it is placed is before any DOM node. If `Child` instead looked like this, no animation would trigger: + +```js [3, 5] +function Component() { + return ( +
+ Hi +
+ ); +} +``` + +
+ +--- +### Animating a shared element {/*animating-a-shared-element*/} + +Normally, we don't recommend assigning a name to a `` and instead let React assign it an automatic name. The reason you might want to assign a name is to animate between completely different components when one tree unmounts and another tree mounts at the same time. To preserve continuity. + +When one tree unmounts and another mounts, if there's a pair where the same name exists in the unmounting tree and the mounting tree, they trigger the "share" animation on both. It animates from the unmounting side to the mounting side. + +Unlike an exit/enter animation this can be deeply inside the deleted/mounted tree. If a `` would also be eligible for exit/enter, then the "share" animation takes precedence. + +If Transition first unmounts one side an then leads to a `` fallback being shown before eventually the new name being mounted, then no shared element transition happens. + +If either the mounted or unmounted side of a pair is outside the viewport, then no pair is formed. This ensures that it doesn't fly in or out of the viewport when something is scrolled. Instead it's treated as a regular enter/exit by itself. + + + +This does not happen if the same Component instance changes position, which triggers an "update". Those animate regardless if one position is outside the viewport. + +There's currently a quirk where if a deeply nested unmounted `` is inside the viewport but the mounted side is not within the viewport, then the unmounted side animates as its own "exit" animation even if it's deeply nested instead of as part of the parent animation. + + + + + +It's important that there's only one thing with the same name mounted at a time in the entire app. Therefore it's important to use unique namespaces for the name to avoid conflicts. To ensure you can do this you might want to add a constant in a separate module that you import. + +```js +export const MY_NAME = "my-globally-unique-name"; +import {MY_NAME} from './shared-name'; +... + +``` + + + + +--- + +### Animating reorder of items in a list {/*animating-reorder-of-items-in-a-list*/} + + +```js +items.map(item => ) +``` + +When reordering a list, without updating the content, the "update" animation triggers on each `` in the list if they're outside a DOM node. Similar to enter/exit animations. + +This means that this will trigger the animation on this ``: + +```js +function Component() { +return
...
; +} +``` + +However, this wouldn't animate each individual item: + +``` +function Component() { + return
...
; +} +``` +Instead, any parent `` would cross-fade. If there is no parent `` then there's no animation in that case. + +This means you might want to avoid a wrapper elements in lists where you want to allow the Component to control its own reorder animation: + +``` +items.map(item =>
) +``` +The above rule also applies if one of the items updates to resize, which then causes the siblings to resize, it'll also animate its sibling `` but only if they're immediate siblings. + +This means that during an update, which causes a lot of re-layout, it doesn't individually animate every `` on the page. That would lead to a lot of noisy animations which distracts from the actual change. Therefore React is more conservative about when an individual animation triggers. + + + +It's important to properly use keys to preserve identity when reordering lists. It might seems like you could use "name", shared element transitions, to animate reorders but that would not trigger if one side was outside the viewport. To animate a reorder you often want to show that it went to a position outside the viewport. + + + +--- + +### Opting-out of an animation {/*opting-out-of-an-animation*/} + +Sometimes you're wrapping a large existing component, like a whole page, and you want to animate some updates to, such as changing the theme. However, you don't want it to opt-in all updates inside the whole page to cross-fade when they're updating. Especially if you're incrementally adding more animations. + +You can use the class "none" to opt-out of an animation. By wrapping your children in a "none" you can disable animations for updates to them while the parent still triggers. + +```js + +
+ + {children} + +
+
+``` + +This will only animate if the theme changes and not if only the children update. The children can still opt-in again with their own `` but at least it's manual again. + +--- + +### Animating from Suspense content {/*animating-from-suspense-content*/} + +Just like any Transition React waits for data and new CSS (``) before running the animation. In addition to this ViewTransitions also wait up to 500ms for new fonts to load before starting the animation to avoid them flickering in later. In the future we plan on also waiting for images. + +If it's inside a new Suspense boundary instance, then the fallback is shown first. After the Suspense boundary fully loads, it triggers the `` to animate the reveal to the content. + +Currently, this only happens for client-side Transition. In the future, this will also animate Suspense boundary for streaming SSR when content from the server suspends during the initial load. + +There are two ways to animate Suspense boundaries depending on where you place the ``: + +Update: + +``` + + }> + + + +``` +In this scenario when the content goes from A to B, it'll be treated as an "update" and apply that class if appropriate. Both A and B will get the same view-transition-name and therefore they're acting as a cross-fade by default. + +Enter/Exit: + +``` +}> + +
+``` + +In this scenario, these are two separate ViewTransition instances each with their own `view-transition-name`. This will be treated as an "exit" of the `` and an "enter of the ``. + +You can achieve different effects depending on where you choose to place the `` boundary. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### My `` is not activating {/*my-viewtransition-is-not-activating*/} + +`` only activates if it is placed is before any DOM node: + +```js [3, 5] +function Component() { + return ( +
+ Hi +
+ ); +} +``` + +To fix, ensure that the `` comes before any other DOM nodes: + +```js [3, 5] +function Component() { + return ( + +
Hi
+
+ ); +} +``` \ No newline at end of file diff --git a/src/sidebarReference.json b/src/sidebarReference.json index a044c9f5baf..2fbae639f06 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -103,6 +103,11 @@ { "title": "", "path": "/reference/react/Suspense" + }, + { + "title": "", + "path": "/reference/react/ViewTransition", + "version": "canary" } ] }, From 58d042f1797b5feac45e9133158249857916e407 Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 25 Mar 2025 22:17:14 -0400 Subject: [PATCH 02/74] Add new error --- src/content/reference/react/ViewTransition.md | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/content/reference/react/ViewTransition.md b/src/content/reference/react/ViewTransition.md index f761aa6f696..0a288224c32 100644 --- a/src/content/reference/react/ViewTransition.md +++ b/src/content/reference/react/ViewTransition.md @@ -351,4 +351,61 @@ function Component() { ); } -``` \ No newline at end of file +``` + +### I'm getting an error "There are two `` components with the same name mounted at the same time." {/*two-viewtransition-with-same-name*/} + +This error occurs when two `` components with the same `name` are mounted at the same time: + + +```js [3] +function Item() { + // 🚩 All items will get the same "name". + return ...; +} + +function ItemList({items}) { + return ( + <> + {item.map(item => )} + + ); +} +``` + +This will cause the View Transition to error. In development, React detects this issue to surface it and logs two errors: + + + + +There are two `` components with the same name mounted at the same time. This is not supported and will cause View Transitions to error. Try to use a more unique name e.g. by using a namespace prefix and adding the id of an item to the name. +{' '}at Item +{' '}at ItemList + + + + + +The existing `` duplicate has this stack trace. +{' '}at Item +{' '}at ItemList + + + + +To fix, ensure that there's only one `` with the same name mounted at a time in the entire app by ensuring the `name` is uniquie, or adding an `id` to the name: + +```js [3] +function Item({id}) { + // βœ… All items will get the same "name". + return ...; +} + +function ItemList({items}) { + return ( + <> + {item.map(item => )} + + ); +} +``` From b23c0c25c1fdc04b894d527bb002f5e21d90506d Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Tue, 25 Mar 2025 23:07:15 -0400 Subject: [PATCH 03/74] Bunch of updates --- src/content/reference/react/ViewTransition.md | 178 +++++++++++++----- 1 file changed, 127 insertions(+), 51 deletions(-) diff --git a/src/content/reference/react/ViewTransition.md b/src/content/reference/react/ViewTransition.md index 0a288224c32..2e13f51640c 100644 --- a/src/content/reference/react/ViewTransition.md +++ b/src/content/reference/react/ViewTransition.md @@ -40,14 +40,20 @@ This API is only available inside React Server Components. ### `` {/*viewtransition*/} -Wrap elements in `` to animate them when they update inside a [Transition](/reference/react/useTransition). Inside a Transition, React uses [heuristics](#heuristics) to determine if a `class` for the View Transition should be added to an element, and adds them for you automatically. The elements are then updated inside [`document.startViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) to trigger the animations. +Wrap elements in `` to animate them when they update inside a [Transition](/reference/react/useTransition). React uses [heuristics](#heuristics) to determine if a View Transition activates for an animation: +- `enter`: If a `ViewTransition` itself gets inserted in this Transition, then this will activate. +- `exit`: If a `ViewTransition` itself gets deleted in this Transition, then this will activate. +- `update`: If a `ViewTransition` has any DOM mutations inside it that React is doing (such as a prop changing) or if the `ViewTransition` boundary itself changes size or position due to an immediate sibling. If there are nested` ViewTransition` then the mutation applies to them and not the parent. +- `share`: If a named `ViewTransition` is inside a deleted subtree and another named `ViewTransition` with the same name is part of an inserted subtree in the same Transition, they form a Shared Element Transition, and it animates from the deleted one to the inserted one. + +By default, `` animates with a smooth cross-fade (the browser default view transition). You can customize the animation by providing a [View Transition Class](#view-transition-class) to the `` component. You can use a customize animations for each kind of trigger (see [Styling View Transitions](#styling-view-transitions)). #### How does `` work? {/*how-does-viewtransition-work*/} -Under the hood, React applies view-transition-name to inline styles of the nearest DOM node nested inside the `` component. If there are multiple siblings DOM nodes like `
` then React adds a suffix to the name to make each unique but conceptually they're part of the same one. React doesn't apply these eagerly but only at the time that boundary should participate in an animation. +Under the hood, React applies `view-transition-name` to inline styles of the nearest DOM node nested inside the `` component. If there are multiple siblings DOM nodes like `
` then React adds a suffix to the name to make each unique but conceptually they're part of the same one. React doesn't apply these eagerly but only at the time that boundary should participate in an animation. React automatically calls `startViewTransition` itself behind the scenes so you should never do that yourself. In fact, if you have something else on the page running a ViewTransition React will interrupt it. So it's recommended that you use React itself to coordinate these. If you had other ways of trigger ViewTransitions in the past, we recommend that you migrate to the built-in way. @@ -73,19 +79,27 @@ After the finished Promise of the `startViewTransition` is resolved, React will #### Props {/*props*/} -* **optional** `default`: A string or object. The [View Transition Class](#view-transition-class) to apply when no other matching trigger is found. +By default, `` animates with a smooth cross-fade. You can customize the animation, or specify a shared element transition, with these props: + * **optional** `enter`: A string or object. The [View Transition Class](#view-transition-class) to apply when enter is activated. * **optional** `exit`: A string or object. The [View Transition Class](#view-transition-class) to apply when exit is activated. * **optional** `update`: A string or object. The [View Transition Class](#view-transition-class) to apply when an update is activated. * **optional** `share`: A string or object. The [View Transition Class](#view-transition-class) to apply when a shared element is activated. -* **optional** `name`: A string or object. The name of the View Transition. If not provided, React will use a unique name for each View Transition. +* **optional** `default`: A string or object. The [View Transition Class](#view-transition-class) used when no other matching activation prop is found. +* **optional** `name`: A string or object. The name of the View Transition used for shared element transitions. If not provided, React will use a unique name for each View Transition to prevent unexpected animations. + +#### Callback {/*events*/} + +These callbacks allow you to adjust the animation imperatively using the [animate](https://developer.mozilla.org/en-US/docs/Web/API/Element/animate) APIs: -#### Events {/*events*/} +* **optional** `onEnter`: A function. React calls `onEnter` after an "enter" animation. +* **optional** `onExit`: A function. React calls `onExit` after an "exit" animation. +* **optional** `onShare`: A function. React calls `onShare` after an "share" animation. +* **optional** `onUpdate`: A function. React calls `onUpdate` after an "update" animation. -* **optional** `onEnter`: A function. React calls `onEnter` after the View Transition animates an enter transition. -* **optional** `onExit`: A function. React calls `onExit` after the View Transition animates an exit transition. -* **optional** `onShare`: A function. React calls `onShare` after the View Transition animates a shared element transition. -* **optional** `onUpdate`: A function. React calls `onUpdate` after the View Transition animates an update transition. +Each callback receives as arguments: +- `element`: The DOM element that was animated. +- `types`: The [transition types)(/todo) included in the animation. ### View Transition Class {/*view-transition-classes*/} @@ -95,36 +109,24 @@ The View Transition Class is the CSS class name(s) applied by React during the t The value `'none'` can be used to prevent a View Transition from activating for a specific trigger. -#### Caveats {/*caveats*/} - -- By default, `setState` updates immediately and does not activate ``, only updates wrapped in a [Transition](/reference/react/useTransition). You can also use [``](/reference/react/Suspense) to opt-in to a Transition to [reveal content](/link-to-suspense-below). -- `` creates an image that can be moved around, scaled and cross-faded. Unlike Layout Animations you may have seen in React Native or Motion, this means that not every individual Element inside of it animates its position. This can lead to better performance and a more continuous feeling, smooth, animation compared to animating every individual piece. However, it can also lose continuity in things that should be moving by themselves. So you might have to add more `` boundaries manually as a result. -- Many users may prefer not having animations on the page. React doesn't automatically disable animations for this case. We recommend that using the `@media (prefers-reduced-motion)` media query to disable animations or tone them down based on user preference. In the future, CSS libraries may have this built-in to their presets. -- Currently, `` only works in the DOM. We're working on adding support for React Native and other platforms. - -### Heuristics {/*heuristics*/} - -Unlike if you manually add [`view-transition-name`](https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name) in the DOM, not all React `` boundaries will activate each time something animates. Instead, React will use a set of built-in heuristic to determine which needs to participate in the current animation. +### Styling View Transitions {/*styling-view-transitions*/} -There are currently four types of triggers: + -- `enter`: If a ViewTransition itself gets inserted in this Transition, then this will activate. -- `exit`: If a ViewTransition itself gets deleted in this Transition, then this will activate. -- `update`: If a ViewTransition has any DOM mutations inside it that React is doing (such as a prop changing) or if the ViewTransition boundary itself changes size or position due to an immediate sibling. If there are nested ViewTransition then the mutation applies to them and not the parent. -- `share`: If a named ViewTransition is inside a deleted subtree and another named ViewTransition with the same name is part of an inserted subtree in the same Transition, they form a Shared Element Transition and it animates from the deleted one to the inserted one. +In many early examples of View Transitions around the web you'll have seen using a [`view-transition-name`](https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name) and then style it using `::view-transition-...(my-name)` selectors. We don't recommend that for styling. Instead, we normally recommend using a View Transition Class instead. -- You can use a different animation for each kind of trigger (see [Styling View Transitions](#styling-view-transitions)). + -### Styling View Transitions {/*styling-view-transitions*/} +To customize the animation for a `` you can provide a View Transition Class to one of the activation props. The View Transition Class is a CSS class name that React applies to the child elements when the ViewTransition activates. -In many early examples of View Transitions around the web you'll have seen using a [`view-transition-name`](https://developer.mozilla.org/en-US/docs/Web/CSS/view-transition-name) and then style it using `::view-transition-...(my-name)` selectors. We don't recommend that for styling. Instead, we normally recommend using a View Transition Class instead. +For example, to customize an "enter" animation, provide a class name to the `enter` prop: -```js [[1, 1, "default"], [2, 1, "my-class"]] - +```js [[1, 1, "enter"], [2, 1, "slide-in"]] + ``` -When the ViewTransition activates, it will add the default class name my-class. Then you can refer to this class using [view transition pseudo selectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API#pseudo-elements) to build reusable animations: +When the ViewTransition activates an "enter" animation, React will add the class name slide-in. Then you can refer to this class using [view transition pseudo selectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API#pseudo-elements) to build reusable animations: ```css ::view-transition-group(.my-class) { @@ -137,8 +139,14 @@ When the ViewTransition activates, it will add the default`, only updates wrapped in a [Transition](/reference/react/useTransition). You can also use [``](/reference/react/Suspense) to opt-in to a Transition to [reveal content](/link-to-suspense-below). +- `` creates an image that can be moved around, scaled and cross-faded. Unlike Layout Animations you may have seen in React Native or Motion, this means that not every individual Element inside of it animates its position. This can lead to better performance and a more continuous feeling, smooth, animation compared to animating every individual piece. However, it can also lose continuity in things that should be moving by themselves. So you might have to add more `` boundaries manually as a result. +- Many users may prefer not having animations on the page. React doesn't automatically disable animations for this case. We recommend that using the `@media (prefers-reduced-motion)` media query to disable animations or tone them down based on user preference. In the future, CSS libraries may have this built-in to their presets. +- Currently, `` only works in the DOM. We're working on adding support for React Native and other platforms. --- @@ -270,26 +278,6 @@ It's important to properly use keys to preserve identity when reordering lists. --- -### Opting-out of an animation {/*opting-out-of-an-animation*/} - -Sometimes you're wrapping a large existing component, like a whole page, and you want to animate some updates to, such as changing the theme. However, you don't want it to opt-in all updates inside the whole page to cross-fade when they're updating. Especially if you're incrementally adding more animations. - -You can use the class "none" to opt-out of an animation. By wrapping your children in a "none" you can disable animations for updates to them while the parent still triggers. - -```js - -
- - {children} - -
-
-``` - -This will only animate if the theme changes and not if only the children update. The children can still opt-in again with their own `` but at least it's manual again. - ---- - ### Animating from Suspense content {/*animating-from-suspense-content*/} Just like any Transition React waits for data and new CSS (``) before running the animation. In addition to this ViewTransitions also wait up to 500ms for new fonts to load before starting the animation to avoid them flickering in later. In the future we plan on also waiting for images. @@ -324,6 +312,94 @@ In this scenario, these are two separate ViewTransition instances each with thei You can achieve different effects depending on where you choose to place the `` boundary. --- +### Opting-out of an animation {/*opting-out-of-an-animation*/} + +Sometimes you're wrapping a large existing component, like a whole page, and you want to animate some updates to, such as changing the theme. However, you don't want it to opt-in all updates inside the whole page to cross-fade when they're updating. Especially if you're incrementally adding more animations. + +You can use the class "none" to opt-out of an animation. By wrapping your children in a "none" you can disable animations for updates to them while the parent still triggers. + +```js + +
+ + {children} + +
+
+``` + +This will only animate if the theme changes and not if only the children update. The children can still opt-in again with their own `` but at least it's manual again. + +--- + +### Customizing `default` animations {/*customizing-default-animations*/} + +TODO + +--- + +### Customizing `enter` animations {/*customizing-enter-animations*/} + +TODO + +--- + +### Customizing `exit` animations {/*customizing-exit-animations*/} + +TODO + +--- + +### Customizing `update` animations {/*customizing-update-animations*/} + +TODO + +--- + +### Customizing `shared` animations {/*customizing-shared-animations*/} + +TODO + +--- + +### Custom animations with types {/*customizing-animations-with-types*/} +You can use the [`addTransitionType`](/TODO) API to add a class name to the child elements when a specific transition type is activated for a specifc activation trigger. This allows you to customize the animation for each type of transition. + +For example, to customize the animation for all forward and backward navigations: + +```js + +
...
+
+ +// in your router: +startTransition(() => { + addTransitionType('navigation-' + navigationType); +}); +``` + +When the ViewTransition activates a "navigation-back" animation, React will add the class name "slide-right". When the ViewTransition activates a "navigation-forward" animation, React will add the class name "slide-left". + +In the future, routers and other libraries may add support for standard view-transition types and styles. + +### Building View Transition enabled routers {/*building-view-transition-enabled-routers*/} + +React waits for any pending Navigation to finish to ensure that scroll restoration happens within the animation. If the Navigation is blocked on React, your router must unblock in `useLayoutEffect` since `useEffect` would lead to a deadlock. + +If a `startTransition` is started from the legacy popstate event, such as during a "back"-navigation then it must finish synchronously to ensure scroll and form restoration works correctly. This is in conflict with running a View Transition animation. Therefore, React will skip animations from popstate. Therefore animations won't run for the back button. You can fix this by upgrading your router to use the Navigation API. + +TODO: add the navigation-forward and navigation-back recommendation? + +--- + +### Adjusting animations {/*adjusting-animations*/} + +TODO: example of using the callbacks with: +- viewTransition.old.animate +- -viewTransition.new.animate ## Troubleshooting {/*troubleshooting*/} From 4a4922df2fbf86530acd09dd58bc2ec00aaceb8d Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 26 Mar 2025 10:41:07 -0400 Subject: [PATCH 04/74] Add addTransitionType --- src/content/reference/react/ViewTransition.md | 8 +- .../reference/react/addTransitionType.md | 182 ++++++++++++++++++ src/sidebarReference.json | 5 + 3 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 src/content/reference/react/addTransitionType.md diff --git a/src/content/reference/react/ViewTransition.md b/src/content/reference/react/ViewTransition.md index 2e13f51640c..ae509174eb4 100644 --- a/src/content/reference/react/ViewTransition.md +++ b/src/content/reference/react/ViewTransition.md @@ -15,8 +15,6 @@ You can try it by upgrading React packages to the most recent experimental versi Experimental versions of React may contain bugs. Don't use them in production. -This API is only available inside React Server Components. - @@ -25,6 +23,8 @@ This API is only available inside React Server Components. ```js +import {unstableViewTransition as ViewTransition} from 'react'; +
...
@@ -99,7 +99,7 @@ These callbacks allow you to adjust the animation imperatively using the [animat Each callback receives as arguments: - `element`: The DOM element that was animated. -- `types`: The [transition types)(/todo) included in the animation. +- `types`: The [Transition Types)(/reference/react/addTransitionType) included in the animation. ### View Transition Class {/*view-transition-classes*/} @@ -363,7 +363,7 @@ TODO --- ### Custom animations with types {/*customizing-animations-with-types*/} -You can use the [`addTransitionType`](/TODO) API to add a class name to the child elements when a specific transition type is activated for a specifc activation trigger. This allows you to customize the animation for each type of transition. +You can use the [`addTransitionType`](/reference/react/addTransitionType) API to add a class name to the child elements when a specific transition type is activated for a specific activation trigger. This allows you to customize the animation for each type of transition. For example, to customize the animation for all forward and backward navigations: diff --git a/src/content/reference/react/addTransitionType.md b/src/content/reference/react/addTransitionType.md new file mode 100644 index 00000000000..9dea1f46789 --- /dev/null +++ b/src/content/reference/react/addTransitionType.md @@ -0,0 +1,182 @@ +--- +title: unstable_addTransitionType +canary: true +--- + + + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + + + + + +`unstable_addTransitionType` lets you specify the cause of a transition. + + +```js +startTransition(() => { + unstable_addTransitionType('my-transition-type'); + setState(newState); +}); +``` + + + + + +--- + +## Reference {/*reference*/} + +### `addTransitionType` {/*addtransitiontype*/} + +#### Parameters {/*parameters*/} + +- `type`: The type of transition to add. This can be any string. + +#### Returns {/*returns*/} + +`startTransition` does not return anything. + +#### Caveats {/*caveats*/} + +- If multiple transitions are combined, all Transition Types are collected. You can also add more than one type to a Transition. +- Transition Types are reset after each commit. This means a `` fallback will associate the types after a `startTransition`, but revealing the content does not. + +--- + +## Usage {/*usage*/} + +### Adding the cause of a transition {/*adding-the-cause-of-a-transition*/} + +Call `addTransitionType` inside of `startTransition` to indicate the cause of a transition: + +``` [[1, 6, "unstable_addTransitionType"], [2, 5, "startTransition", [3, 6, "'submit-click'"]] +import { startTransition, unstable_addTransitionType } from 'react'; + +function Submit({action) { + function handleClick() { + startTransition(() => { + unstable_addTransitionType('submit-click'); + action(); + }); + } + + return ; +} + +``` + +When you call addTransitionType inside the scope of startTransition, React will associate submit-click as one of the causes for the Transition. + +Currently, Transition Types can be used to customize different animations based on what caused the Transition. You have three different ways to choose from for how to use them: + +- [Customize animations using browser view transition types](#customize-animations-using-browser-view-transition-types) +- [Customize animations using `View Transition` Class](#customize-animations-using-view-transition-class) +- [Customize animations using `ViewTransiton` events](#customize-animations-using-viewtransition-events) + +In the future, we plan to support more use cases for using the cause of a transition. + +--- +### Customize animations using browser view transition types {/*customize-animations-using-browser-view-transition-types*/} + +When a [`ViewTransition`](/reference/react/ViewTransition) activates from a transition, React adds all the Transition Types as browser [view transition types](https://www.w3.org/TR/css-view-transitions-2/#active-view-transition-pseudo-examples) to the element. + +This allows you to customize different animations based on CSS scopes: + +```js [11] +function Component() { + return ( + +
Hello
+
+ ); +} + +startTransition(() => { + unstable_addTransitionType('my-transition-type'); + setShow(true); +}); +``` + +```css +:root:active-view-transition-type(my-transition-type) { + &::view-transition-...(...) { + ... + } +} +``` + +--- + +### Customize animations using `View Transition` Class {/*customize-animations-using-view-transition-class*/} + +You can customize animations for an activated `ViewTransition` based on type by passing an object to the View Transition Class: + +```js +function Component() { + return ( + +
Hello
+
+ ); +} + +// ... +startTransition(() => { + unstable_addTransitionType('my-transition-type'); + setState(newState); +}); +``` + +If multiple types match, then they're joined together. If no types match then the special "default" entry is used instead. If any type has the value "none" then that wins and the ViewTransition is disabled (not assigned a name). + +These can be combined with enter/exit/update/layout/share props to match based on kind of trigger and Transition Type. + +```js + +``` + +--- + +### Customize animations using `ViewTransiton` events {/*customize-animations-using-viewtransition-events*/} + +You can imperatively customize animations for an activated `ViewTransition` based on type using View Transition events: + +``` + { + if (types.includes('navigation-back')) { + ... + } else if (types.includes('navigation-forward')) { + ... + } else { + ... + } +}}> +``` + +This allows you to pick different imperative Animations based on the cause. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### TODO {/*todo2*/} diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 2fbae639f06..43923a6942c 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -157,6 +157,11 @@ "title": "captureOwnerStack", "path": "/reference/react/captureOwnerStack", "version": "canary" + }, + { + "title": "unstable_addTransitionType", + "path": "/reference/react/addTransitionType", + "version": "canary" } ] }, From aec9b5500614769b304bb6a037a1dade675b4d8f Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Wed, 26 Mar 2025 11:16:47 -0400 Subject: [PATCH 05/74] Add Activity --- src/content/reference/react/Activity.md | 76 +++++++++++++++++++++++++ src/sidebarReference.json | 5 ++ 2 files changed, 81 insertions(+) create mode 100644 src/content/reference/react/Activity.md diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md new file mode 100644 index 00000000000..ed22f9c426b --- /dev/null +++ b/src/content/reference/react/Activity.md @@ -0,0 +1,76 @@ +--- +title: +canary: true +--- + + + +**This API is experimental and is not available in a stable version of React yet.** + +You can try it by upgrading React packages to the most recent experimental version: + +- `react@experimental` +- `react-dom@experimental` +- `eslint-plugin-react-hooks@experimental` + +Experimental versions of React may contain bugs. Don't use them in production. + + + + + +`` lets you hide and show part of the UI. + + +```js +import {unstableActivty as Activiy} from 'react'; + + + + +``` + + + + + +--- + +## Reference {/*reference*/} + +### `` {/*activity*/} + +Wrap elements in `` to... + +- `hidden`: TODO +- `visible`: TODO + + + + +#### How does `` work? {/*how-does-activity-work*/} + +Under the hood... + + + +#### Props {/*props*/} + +* **optional** `mode`: + +#### Caveats {/*caveats*/} + +- TODO + +--- + + +## Usage {/*usage*/} + +### TODO {/*todo*/} + +--- + +## Troubleshooting {/*troubleshooting*/} + +### TODO {/*troubleshooting-todo*/} diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 43923a6942c..e9bfc5c65bc 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -104,6 +104,11 @@ "title": "", "path": "/reference/react/Suspense" }, + { + "title": "", + "path": "/reference/react/Activity", + "version": "canary" + }, { "title": "", "path": "/reference/react/ViewTransition", From f2288d3691d960dda32757d751c633b492720ffa Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Thu, 27 Mar 2025 10:38:14 -0400 Subject: [PATCH 06/74] Expand on Activity --- src/content/reference/react/Activity.md | 419 +++++++++++++++++++++++- 1 file changed, 408 insertions(+), 11 deletions(-) diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index ed22f9c426b..a1b47aaf164 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -23,10 +23,8 @@ Experimental versions of React may contain bugs. Don't use them in production. ```js -import {unstableActivty as Activiy} from 'react'; - - - + + ``` @@ -40,10 +38,19 @@ import {unstableActivty as Activiy} from 'react'; ### `` {/*activity*/} -Wrap elements in `` to... +Wrap a part of the UI in `` to manage its visibility state: + +```js +import {unstableActivity as Activity} from 'react'; + + + + +``` -- `hidden`: TODO -- `visible`: TODO +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 does not mount by creating effects. When a "visible" Activity switches to "hidden" it conceptually unmounts by destroying all the effects, but saves it's state. This allows fast switching between "visible" and "hidden" states without re-creating the state for a "hidden" Activity. + +In the future, "hidden" Activities may automatically destroy state based on resources like memory. @@ -56,18 +63,408 @@ Under the hood... #### Props {/*props*/} -* **optional** `mode`: +* `children`: The actual UI you intend to render. +* **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*/} -- TODO +- While hidden, the `children` of `` are hidden on the page. +- `` will unmount all effects when switching from "visible" to "hidden" without destroying React or DOM state. This means effects that expect to only run "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. +- Parts of the UI wrapped in `` are not included in the SSR response. +- 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. --- - ## Usage {/*usage*/} -### TODO {/*todo*/} +### Pre-render part of the UI {/*pre-render-part-of-the-ui*/} + +You can pre-render part of the UI using ``: + +```js [[1, 1, "Activity"], [1, 3, "Activity"], [2, 1, "mode"], [3, 1, "visible"], [4, 1, "hidden"]] + + + +``` + +When an Activity is rendered with mode "hidden", the `children` are not visible on the page, but are rendered at lower prioirty than the visible content on the page. + +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. + +In the follow 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: + + + +```js +import { Suspense, useState } from 'react'; +import TabButton from './TabButton.js'; +import AboutTab from './AboutTab.js'; +import PostsTab from './PostsTab.js'; +import ContactTab from './ContactTab.js'; + +export default function TabContainer() { + const [tab, setTab] = useState('about'); + return ( + πŸŒ€ Loading...}> + setTab('about')} + > + About + + setTab('posts')} + > + Posts + + setTab('contact')} + > + Contact + +
+ {tab === 'about' && } + {tab === 'posts' && } + {tab === 'contact' && } +
+ ); +} +``` + + +```js src/TabButton.js active +import { useTransition } from 'react'; + +export default function TabButton({ action, children, isActive }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return {children} + } + if (isPending) { + return {children}; + } + return ( + + ); +} +``` + +```js src/AboutTab.js hidden +import {unstable_ViewTransition as ViewTransition} from 'react'; + +export default function AboutTab() { + return ( + +

Welcome to my profile!

+
+ ); +} +``` + +```js src/PostsTab.js hidden +import {use, unstable_ViewTransition as ViewTransition} from 'react'; +import { fetchData } from './data.js'; + +function PostsTab() { + const posts = use(fetchData('/posts')); + return ( + +
    + {posts.map(post => + + )} +
+
+ ); +} + +function Post({ title }) { + return ( +
  • + {title} +
  • + ); +} + +export default PostsTab; +``` + +```js src/ContactTab.js hidden +import {unstable_ViewTransition as ViewTransition} from 'react'; + +export default function ContactTab() { + return ( + +

    + You can find me online here: +

    +
      +
    • admin@mysite.com
    • +
    • +123456789
    • +
    +
    + ); +} +``` + + +```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.startsWith('/posts')) { + return await getPosts(); + } else { + throw Error('Not implemented'); + } +} + +async function getPosts() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + let posts = []; + for (let i = 0; i < 500; i++) { + posts.push({ + id: i, + title: 'Post #' + (i + 1) + }); + } + return posts; +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +.pending { color: #777; } +``` + +```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" + } +} +``` + +
    + +In this example, the user needs to wait for the posts to load when clicking on the "Posts" tab. + +We can reduce the delay for the "Posts" tab by pre-rendering the inactive Tabs with a hidden ``: + + + +```js +import { Suspense, useState, unstable_Activity as Activity } from "react"; +import TabButton from "./TabButton.js"; +import AboutTab from "./AboutTab.js"; +import PostsTab from "./PostsTab.js"; +import ContactTab from "./ContactTab.js"; + +export default function TabContainer() { + const [tab, setTab] = useState("about"); + return ( + πŸŒ€ Loading...}> + setTab("about")}> + About + + setTab("posts")}> + Posts + + setTab("contact")}> + Contact + +
    + + + + + + + + + +
    + ); +} +``` + + +```js src/TabButton.js active +import { useTransition } from 'react'; + +export default function TabButton({ action, children, isActive }) { + const [isPending, startTransition] = useTransition(); + if (isActive) { + return {children} + } + if (isPending) { + return {children}; + } + return ( + + ); +} +``` + +```js src/AboutTab.js hidden +import {unstable_ViewTransition as ViewTransition} from 'react'; + +export default function AboutTab() { + return ( + +

    Welcome to my profile!

    +
    + ); +} +``` + +```js src/PostsTab.js hidden +import {use, unstable_ViewTransition as ViewTransition} from 'react'; +import { fetchData } from './data.js'; + +function PostsTab() { + const posts = use(fetchData('/posts')); + return ( + +
      + {posts.map(post => + + )} +
    +
    + ); +} + +function Post({ title }) { + return ( +
  • + {title} +
  • + ); +} + +export default PostsTab; +``` + +```js src/ContactTab.js hidden +import {unstable_ViewTransition as ViewTransition} from 'react'; + +export default function ContactTab() { + return ( + +

    + You can find me online here: +

    +
      +
    • admin@mysite.com
    • +
    • +123456789
    • +
    +
    + ); +} +``` + + +```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.startsWith('/posts')) { + return await getPosts(); + } else { + throw Error('Not implemented'); + } +} + +async function getPosts() { + // Add a fake delay to make waiting noticeable. + await new Promise(resolve => { + setTimeout(resolve, 1000); + }); + let posts = []; + for (let i = 0; i < 500; i++) { + posts.push({ + id: i, + title: 'Post #' + (i + 1) + }); + } + return posts; +} +``` + +```css +button { margin-right: 10px } +b { display: inline-block; margin-right: 10px; } +.pending { color: #777; } +``` + +```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" + } +} +``` + +
    --- From bf093e96d4d699cb76cd7818581c92d05c320b7e Mon Sep 17 00:00:00 2001 From: Rick Hanlon Date: Thu, 27 Mar 2025 12:36:26 -0400 Subject: [PATCH 07/74] Expand more on Activity --- src/content/reference/react/Activity.md | 695 +++++++++++++++++++++++- 1 file changed, 692 insertions(+), 3 deletions(-) diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index a1b47aaf164..72c4079db6a 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -64,12 +64,12 @@ Under the hood... #### Props {/*props*/} * `children`: The actual UI you intend to render. -* **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. +* **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. -- `` will unmount all effects when switching from "visible" to "hidden" without destroying React or DOM state. This means effects that expect to only run "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. +- `` will unmount all Effects when switching from "visible" to "hidden" without destroying React or DOM state. This means Effects that expect to only run "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. - Parts of the UI wrapped in `` are not included in the SSR response. - 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. @@ -203,6 +203,10 @@ import {unstable_ViewTransition as ViewTransition} from 'react'; export default function ContactTab() { return ( +

    + Send me a message! +

    +