From 56f2312947abb4ea9974e64908afa6d97a002958 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 17 Mar 2025 15:59:17 -0400 Subject: [PATCH] Materialize the tree ID when ViewTransition name=auto consumes one --- .../view-transition/src/components/Page.js | 18 ++++++++-- .../src/ReactFiberBeginWork.js | 3 ++ packages/react-server/src/ReactFizzServer.js | 33 ++++++++++++++++--- 3 files changed, 47 insertions(+), 7 deletions(-) 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}
:
Loading
}

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