@samitouri / QOS-React-2 / commits / 845d93742f

Remove useId semantics from View Transition name generation (#33094)

Originally I thought it was important that SSR used the same View Transition name as the client so that the Fizz runtime could emit those names and then the client could pick up and take over. However, I no longer believe that approach is feasible. Instead, the names can be generated only during that particular animation. Therefore we can simplify the auto name assignment to not have to consider the hydration.

Sebastian Markbåge committed May 6, 2025 at 10:33 UTC 845d93742fb090e7a35abea409a55e2a14613255
4 files changed +15 -64
packages/react-reconciler/src/ReactFiberBeginWork.js
-13
@@ -36,8 +36,6 @@ import type {
36 OffscreenQueue,
37 OffscreenInstance,
38 } from './ReactFiberOffscreenComponent';
39 -import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
40 -import {assignViewTransitionAutoName} from './ReactFiberViewTransitionComponent';
39 import type {
40 Cache,
41 CacheComponentState,
@@ -3538,7 +3536,6 @@ function updateViewTransition(
3536 renderLanes: Lanes,
3537 ) {
3538 const pendingProps: ViewTransitionProps = workInProgress.pendingProps;
3541 - const instance: ViewTransitionState = workInProgress.stateNode;
3539 if (pendingProps.name != null && pendingProps.name !== 'auto') {
3540 // Explicitly named boundary. We track it so that we can pair it up with another explicit
3541 // boundary if we get deleted.
@@ -3546,16 +3543,6 @@ function updateViewTransition(
3543 current === null
3544 ? ViewTransitionNamedMount | ViewTransitionNamedStatic
3545 : ViewTransitionNamedStatic;
3549 - } else {
3550 - // Assign an auto generated name using the useId algorthim if an explicit one is not provided.
3551 - // We don't need the name yet but we do it here to allow hydration state to be used.
3552 - // We might end up needing these to line up if we want to Transition from dehydrated fallback
3553 - // to client rendered content. If we don't end up using that we could just assign an incremeting
3554 - // counter in the commit phase instead.
3555 - assignViewTransitionAutoName(pendingProps, instance);
3556 - if (getIsHydrating()) {
3557 - pushMaterializedTreeId(workInProgress);
3558 - }
3546 }
3547 if (__DEV__) {
3548 // $FlowFixMe[prop-missing]
packages/react-reconciler/src/ReactFiberViewTransitionComponent.js
+10 -34
@@ -12,14 +12,10 @@ import type {FiberRoot} from './ReactInternalTypes';
12 import type {ViewTransitionInstance, Instance} from './ReactFiberConfig';
13
14 import {
15 - getWorkInProgressRoot,
15 + getCommittingRoot,
16 getPendingTransitionTypes,
17 } from './ReactFiberWorkLoop';
18
19 -import {getIsHydrating} from './ReactFiberHydrationContext';
20 -
21 -import {getTreeId} from './ReactFiberTreeContext';
22 -
19 export type ViewTransitionState = {
20 autoName: null | string, // the view-transition-name to use when an explicit one is not specified
21 paired: null | ViewTransitionState, // a temporary state during the commit phase if we have paired this with another instance
@@ -29,47 +25,27 @@ export type ViewTransitionState = {
25
26 let globalClientIdCounter: number = 0;
27
32 -export function assignViewTransitionAutoName(
28 +export function getViewTransitionName(
29 props: ViewTransitionProps,
30 instance: ViewTransitionState,
31 ): string {
32 + if (props.name != null && props.name !== 'auto') {
33 + return props.name;
34 + }
35 if (instance.autoName !== null) {
36 return instance.autoName;
37 }
38
40 - const root = ((getWorkInProgressRoot(): any): FiberRoot);
39 + // We assume we always call this in the commit phase.
40 + const root = ((getCommittingRoot(): any): FiberRoot);
41 const identifierPrefix = root.identifierPrefix;
42 -
43 - let name;
44 - if (getIsHydrating()) {
45 - const treeId = getTreeId();
46 - // Use a captial R prefix for server-generated ids.
47 - name = '\u00AB' + identifierPrefix + 'T' + treeId + '\u00BB';
48 - } else {
49 - // Use a lowercase r prefix for client-generated ids.
50 - const globalClientId = globalClientIdCounter++;
51 - name =
52 - '\u00AB' +
53 - identifierPrefix +
54 - 't' +
55 - globalClientId.toString(32) +
56 - '\u00BB';
57 - }
42 + const globalClientId = globalClientIdCounter++;
43 + const name =
44 + '\u00AB' + identifierPrefix + 't' + globalClientId.toString(32) + '\u00BB';
45 instance.autoName = name;
46 return name;
47 }
48
62 -export function getViewTransitionName(
63 - props: ViewTransitionProps,
64 - instance: ViewTransitionState,
65 -): string {
66 - if (props.name != null && props.name !== 'auto') {
67 - return props.name;
68 - }
69 - // We should have assigned a name by now.
70 - return (instance.autoName: any);
71 -}
72 -
49 function getClassNameByType(classByType: ?ViewTransitionClass): ?string {
50 if (classByType == null || typeof classByType === 'string') {
51 return classByType;
packages/react-reconciler/src/ReactFiberWorkLoop.js
+4
@@ -700,6 +700,10 @@ export function getWorkInProgressRoot(): FiberRoot | null {
700 return workInProgressRoot;
701 }
702
703 +export function getCommittingRoot(): FiberRoot | null {
704 + return pendingEffectsRoot;
705 +}
706 +
707 export function getWorkInProgressRootRenderLanes(): Lanes {
708 return workInProgressRootRenderLanes;
709 }
packages/react-server/src/ReactFizzServer.js
+1 -17
@@ -2274,23 +2274,7 @@ function renderViewTransition(
2274 ) {
2275 const prevKeyPath = task.keyPath;
2276 task.keyPath = keyPath;
2277 - if (props.name != null && props.name !== 'auto') {
2278 - renderNodeDestructive(request, task, props.children, -1);
2279 - } else {
2280 - // This will be auto-assigned a name which claims a "useId" slot.
2281 - // This component materialized an id. We treat this as its own level, with
2282 - // a single "child" slot.
2283 - const prevTreeContext = task.treeContext;
2284 - const totalChildren = 1;
2285 - const index = 0;
2286 - // Modify the id context. Because we'll need to reset this if something
2287 - // suspends or errors, we'll use the non-destructive render path.
2288 - task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
2289 - renderNode(request, task, props.children, -1);
2290 - // Like the other contexts, this does not need to be in a finally block
2291 - // because renderNode takes care of unwinding the stack.
2292 - task.treeContext = prevTreeContext;
2293 - }
2277 + renderNodeDestructive(request, task, props.children, -1);
2278 task.keyPath = prevKeyPath;
2279 }
2280