Skip to content

Remove useId semantics from View Transition name generation #33094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ import type {
OffscreenQueue,
OffscreenInstance,
} from './ReactFiberOffscreenComponent';
import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
import {assignViewTransitionAutoName} from './ReactFiberViewTransitionComponent';
import type {
Cache,
CacheComponentState,
Expand Down Expand Up @@ -3538,24 +3536,13 @@ function updateViewTransition(
renderLanes: Lanes,
) {
const pendingProps: ViewTransitionProps = workInProgress.pendingProps;
const instance: ViewTransitionState = workInProgress.stateNode;
if (pendingProps.name != null && pendingProps.name !== 'auto') {
// Explicitly named boundary. We track it so that we can pair it up with another explicit
// boundary if we get deleted.
workInProgress.flags |=
current === null
? ViewTransitionNamedMount | ViewTransitionNamedStatic
: ViewTransitionNamedStatic;
} else {
// Assign an auto generated name using the useId algorthim if an explicit one is not provided.
// We don't need the name yet but we do it here to allow hydration state to be used.
// We might end up needing these to line up if we want to Transition from dehydrated fallback
// 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 (__DEV__) {
// $FlowFixMe[prop-missing]
Expand Down
44 changes: 10 additions & 34 deletions packages/react-reconciler/src/ReactFiberViewTransitionComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ import type {FiberRoot} from './ReactInternalTypes';
import type {ViewTransitionInstance, Instance} from './ReactFiberConfig';

import {
getWorkInProgressRoot,
getCommittingRoot,
getPendingTransitionTypes,
} from './ReactFiberWorkLoop';

import {getIsHydrating} from './ReactFiberHydrationContext';

import {getTreeId} from './ReactFiberTreeContext';

export type ViewTransitionState = {
autoName: null | string, // the view-transition-name to use when an explicit one is not specified
paired: null | ViewTransitionState, // a temporary state during the commit phase if we have paired this with another instance
Expand All @@ -29,47 +25,27 @@ export type ViewTransitionState = {

let globalClientIdCounter: number = 0;

export function assignViewTransitionAutoName(
export function getViewTransitionName(
props: ViewTransitionProps,
instance: ViewTransitionState,
): string {
if (props.name != null && props.name !== 'auto') {
return props.name;
}
if (instance.autoName !== null) {
return instance.autoName;
}

const root = ((getWorkInProgressRoot(): any): FiberRoot);
// We assume we always call this in the commit phase.
const root = ((getCommittingRoot(): any): FiberRoot);
const identifierPrefix = root.identifierPrefix;

let name;
if (getIsHydrating()) {
const treeId = getTreeId();
// Use a captial R prefix for server-generated ids.
name = '\u00AB' + identifierPrefix + 'T' + treeId + '\u00BB';
} else {
// Use a lowercase r prefix for client-generated ids.
const globalClientId = globalClientIdCounter++;
name =
'\u00AB' +
identifierPrefix +
't' +
globalClientId.toString(32) +
'\u00BB';
}
const globalClientId = globalClientIdCounter++;
const name =
'\u00AB' + identifierPrefix + 't' + globalClientId.toString(32) + '\u00BB';
instance.autoName = name;
return name;
}

export function getViewTransitionName(
props: ViewTransitionProps,
instance: ViewTransitionState,
): string {
if (props.name != null && props.name !== 'auto') {
return props.name;
}
// We should have assigned a name by now.
return (instance.autoName: any);
}

function getClassNameByType(classByType: ?ViewTransitionClass): ?string {
if (classByType == null || typeof classByType === 'string') {
return classByType;
Expand Down
4 changes: 4 additions & 0 deletions packages/react-reconciler/src/ReactFiberWorkLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,10 @@ export function getWorkInProgressRoot(): FiberRoot | null {
return workInProgressRoot;
}

export function getCommittingRoot(): FiberRoot | null {
return pendingEffectsRoot;
}

export function getWorkInProgressRootRenderLanes(): Lanes {
return workInProgressRootRenderLanes;
}
Expand Down
18 changes: 1 addition & 17 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2274,23 +2274,7 @@ function renderViewTransition(
) {
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;
}
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
}

Expand Down