diff --git a/fixtures/view-transition/src/components/Page.js b/fixtures/view-transition/src/components/Page.js
index d7acc93c0cb19..19313a99e372d 100644
--- a/fixtures/view-transition/src/components/Page.js
+++ b/fixtures/view-transition/src/components/Page.js
@@ -4,6 +4,7 @@ import React, {
unstable_useSwipeTransition as useSwipeTransition,
useEffect,
useState,
+ useId,
} from 'react';
import SwipeRecognizer from './SwipeRecognizer';
@@ -39,6 +40,11 @@ function Component() {
);
}
+function Id() {
+ // This is just testing that Id inside a ViewTransition can hydrate correctly.
+ return ;
+}
+
export default function Page({url, navigate}) {
const [renderedUrl, startGesture] = useSwipeTransition('/?a', url, '/?b');
const show = renderedUrl === '/?b';
@@ -77,8 +83,12 @@ export default function Page({url, navigate}) {
-
- {!show ? 'A' : 'B'}
+
+
+
+ {!show ? 'A' : 'B'}
+
+
hello{exclamation} : }
scroll me
-
+
+
+
diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js
index b37bd4ea3f44f..9dbd97a477f30 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.js
@@ -3257,6 +3257,9 @@ function updateViewTransition(
// to client rendered content. If we don't end up using that we could just assign an incremeting
// counter in the commit phase instead.
assignViewTransitionAutoName(pendingProps, instance);
+ if (getIsHydrating()) {
+ pushMaterializedTreeId(workInProgress);
+ }
}
if (current !== null && current.memoizedProps.name !== pendingProps.name) {
// If the name changes, we schedule a ref effect to create a new ref instance.
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index b87b8955f3654..a5bea54269bb9 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -2211,6 +2211,34 @@ function renderOffscreen(
}
}
+function renderViewTransition(
+ request: Request,
+ task: Task,
+ keyPath: KeyNode,
+ props: Object,
+) {
+ const prevKeyPath = task.keyPath;
+ task.keyPath = keyPath;
+ if (props.name != null && props.name !== 'auto') {
+ renderNodeDestructive(request, task, props.children, -1);
+ } else {
+ // This will be auto-assigned a name which claims a "useId" slot.
+ // This component materialized an id. We treat this as its own level, with
+ // a single "child" slot.
+ const prevTreeContext = task.treeContext;
+ const totalChildren = 1;
+ const index = 0;
+ // Modify the id context. Because we'll need to reset this if something
+ // suspends or errors, we'll use the non-destructive render path.
+ task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
+ renderNode(request, task, props.children, -1);
+ // Like the other contexts, this does not need to be in a finally block
+ // because renderNode takes care of unwinding the stack.
+ task.treeContext = prevTreeContext;
+ }
+ task.keyPath = prevKeyPath;
+}
+
function renderElement(
request: Request,
task: Task,
@@ -2267,10 +2295,7 @@ function renderElement(
}
case REACT_VIEW_TRANSITION_TYPE: {
if (enableViewTransition) {
- const prevKeyPath = task.keyPath;
- task.keyPath = keyPath;
- renderNodeDestructive(request, task, props.children, -1);
- task.keyPath = prevKeyPath;
+ renderViewTransition(request, task, keyPath, props);
return;
}
// Fallthrough