Skip to content

Commit 9fde224

Browse files
authored
Materialize the tree ID when ViewTransition name=auto consumes one (facebook#32651)
ViewTransition uses the `useId` algorithm to auto-assign names. This ensures that we could animate between SSR content and client content by ensuring that the names line up. However, I missed that we need to bump the id (materialize it) when we do that. This is what function components do if they use one or more `useId()`. This caused duplicate names when two ViewTransitions were nested without any siblings since they would share name.
1 parent ca02c4b commit 9fde224

File tree

3 files changed

+47
-7
lines changed

3 files changed

+47
-7
lines changed

fixtures/view-transition/src/components/Page.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, {
44
unstable_useSwipeTransition as useSwipeTransition,
55
useEffect,
66
useState,
7+
useId,
78
} from 'react';
89

910
import SwipeRecognizer from './SwipeRecognizer';
@@ -39,6 +40,11 @@ function Component() {
3940
);
4041
}
4142

43+
function Id() {
44+
// This is just testing that Id inside a ViewTransition can hydrate correctly.
45+
return <span id={useId()} />;
46+
}
47+
4248
export default function Page({url, navigate}) {
4349
const [renderedUrl, startGesture] = useSwipeTransition('/?a', url, '/?b');
4450
const show = renderedUrl === '/?b';
@@ -77,8 +83,12 @@ export default function Page({url, navigate}) {
7783
</button>
7884
<ViewTransition className="none">
7985
<div>
80-
<ViewTransition className={transitions['slide-on-nav']}>
81-
<h1>{!show ? 'A' : 'B'}</h1>
86+
<ViewTransition>
87+
<div>
88+
<ViewTransition className={transitions['slide-on-nav']}>
89+
<h1>{!show ? 'A' : 'B'}</h1>
90+
</ViewTransition>
91+
</div>
8292
</ViewTransition>
8393
<ViewTransition
8494
className={{
@@ -102,7 +112,9 @@ export default function Page({url, navigate}) {
102112
{show ? <div>hello{exclamation}</div> : <section>Loading</section>}
103113
</ViewTransition>
104114
<p>scroll me</p>
105-
<p></p>
115+
<p>
116+
<Id />
117+
</p>
106118
<p></p>
107119
<p></p>
108120
<p></p>

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3298,6 +3298,9 @@ function updateViewTransition(
32983298
// to client rendered content. If we don't end up using that we could just assign an incremeting
32993299
// counter in the commit phase instead.
33003300
assignViewTransitionAutoName(pendingProps, instance);
3301+
if (getIsHydrating()) {
3302+
pushMaterializedTreeId(workInProgress);
3303+
}
33013304
}
33023305
if (current !== null && current.memoizedProps.name !== pendingProps.name) {
33033306
// If the name changes, we schedule a ref effect to create a new ref instance.

packages/react-server/src/ReactFizzServer.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,34 @@ function renderOffscreen(
22112211
}
22122212
}
22132213

2214+
function renderViewTransition(
2215+
request: Request,
2216+
task: Task,
2217+
keyPath: KeyNode,
2218+
props: Object,
2219+
) {
2220+
const prevKeyPath = task.keyPath;
2221+
task.keyPath = keyPath;
2222+
if (props.name != null && props.name !== 'auto') {
2223+
renderNodeDestructive(request, task, props.children, -1);
2224+
} else {
2225+
// This will be auto-assigned a name which claims a "useId" slot.
2226+
// This component materialized an id. We treat this as its own level, with
2227+
// a single "child" slot.
2228+
const prevTreeContext = task.treeContext;
2229+
const totalChildren = 1;
2230+
const index = 0;
2231+
// Modify the id context. Because we'll need to reset this if something
2232+
// suspends or errors, we'll use the non-destructive render path.
2233+
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
2234+
renderNode(request, task, props.children, -1);
2235+
// Like the other contexts, this does not need to be in a finally block
2236+
// because renderNode takes care of unwinding the stack.
2237+
task.treeContext = prevTreeContext;
2238+
}
2239+
task.keyPath = prevKeyPath;
2240+
}
2241+
22142242
function renderElement(
22152243
request: Request,
22162244
task: Task,
@@ -2267,10 +2295,7 @@ function renderElement(
22672295
}
22682296
case REACT_VIEW_TRANSITION_TYPE: {
22692297
if (enableViewTransition) {
2270-
const prevKeyPath = task.keyPath;
2271-
task.keyPath = keyPath;
2272-
renderNodeDestructive(request, task, props.children, -1);
2273-
task.keyPath = prevKeyPath;
2298+
renderViewTransition(request, task, keyPath, props);
22742299
return;
22752300
}
22762301
// Fallthrough

0 commit comments

Comments
 (0)