Skip to content

Commit 8ac25e5

Browse files
authored
Warn for duplicate ViewTransition names (#32752)
This adds early logging when two ViewTransitions with the same name are mounted at the same time. Whether they're part of a View Transition or not. This lets us include the owner stack of each one. I do two logs so that you can get the stack trace of each one of the duplicates. It currently only logs once for each name which also avoids the scenario when you have many hits for the same name in one commit. However, we could also possibly log a stack for each of them but seems noisy. Currently we don't log if a SwipeTransition is the first time the pair gets mounted which could lead to a View Transition error before we've warned. That could be a separate improvement.
1 parent f9e1b16 commit 8ac25e5

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
ForceClientRender,
110110
DidCapture,
111111
AffectedParentLayout,
112+
ViewTransitionNamedStatic,
112113
} from './ReactFiberFlags';
113114
import {
114115
commitStartTime,
@@ -254,6 +255,10 @@ import {
254255
pushMutationContext,
255256
popMutationContext,
256257
} from './ReactFiberMutationTracking';
258+
import {
259+
trackNamedViewTransition,
260+
untrackNamedViewTransition,
261+
} from './ReactFiberDuplicateViewTransitions';
257262

258263
// Used during the commit phase to track the state of the Offscreen component stack.
259264
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
@@ -738,6 +743,11 @@ function commitLayoutEffectOnFiber(
738743
}
739744
case ViewTransitionComponent: {
740745
if (enableViewTransition) {
746+
if (__DEV__) {
747+
if (flags & ViewTransitionNamedStatic) {
748+
trackNamedViewTransition(finishedWork);
749+
}
750+
}
741751
recursivelyTraverseLayoutEffects(
742752
finishedRoot,
743753
finishedWork,
@@ -1551,11 +1561,34 @@ function commitDeletionEffectsOnFiber(
15511561
}
15521562
break;
15531563
}
1564+
case ViewTransitionComponent: {
1565+
if (enableViewTransition) {
1566+
if (__DEV__) {
1567+
if (deletedFiber.flags & ViewTransitionNamedStatic) {
1568+
untrackNamedViewTransition(deletedFiber);
1569+
}
1570+
}
1571+
safelyDetachRef(deletedFiber, nearestMountedAncestor);
1572+
recursivelyTraverseDeletionEffects(
1573+
finishedRoot,
1574+
nearestMountedAncestor,
1575+
deletedFiber,
1576+
);
1577+
return;
1578+
}
1579+
// Fallthrough
1580+
}
15541581
case Fragment: {
15551582
if (enableFragmentRefs) {
15561583
if (!offscreenSubtreeWasHidden) {
15571584
safelyDetachRef(deletedFiber, nearestMountedAncestor);
15581585
}
1586+
recursivelyTraverseDeletionEffects(
1587+
finishedRoot,
1588+
nearestMountedAncestor,
1589+
deletedFiber,
1590+
);
1591+
return;
15591592
}
15601593
// Fallthrough
15611594
}
@@ -2594,6 +2627,11 @@ export function disappearLayoutEffects(finishedWork: Fiber) {
25942627
}
25952628
case ViewTransitionComponent: {
25962629
if (enableViewTransition) {
2630+
if (__DEV__) {
2631+
if (finishedWork.flags & ViewTransitionNamedStatic) {
2632+
untrackNamedViewTransition(finishedWork);
2633+
}
2634+
}
25972635
safelyDetachRef(finishedWork, finishedWork.return);
25982636
}
25992637
recursivelyTraverseDisappearLayoutEffects(finishedWork);
@@ -2803,6 +2841,11 @@ export function reappearLayoutEffects(
28032841
finishedWork,
28042842
includeWorkInProgressEffects,
28052843
);
2844+
if (__DEV__) {
2845+
if (flags & ViewTransitionNamedStatic) {
2846+
trackNamedViewTransition(finishedWork);
2847+
}
2848+
}
28062849
safelyAttachRef(finishedWork, finishedWork.return);
28072850
break;
28082851
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {Fiber} from './ReactInternalTypes';
11+
import type {ViewTransitionProps} from './ReactFiberViewTransitionComponent';
12+
import {runWithFiberInDEV} from './ReactCurrentFiber';
13+
14+
// Use in DEV to track mounted named ViewTransitions. This is used to warn for
15+
// duplicate names. This should technically be tracked per Document because you could
16+
// have two different documents that can have separate namespaces, but to keep things
17+
// simple we just use a global Map. Technically it should also include any manually
18+
// assigned view-transition-name outside React too.
19+
const mountedNamedViewTransitions: Map<string, Fiber> = __DEV__
20+
? new Map()
21+
: (null: any);
22+
const didWarnAboutName: {[string]: boolean} = __DEV__ ? {} : (null: any);
23+
24+
export function trackNamedViewTransition(fiber: Fiber): void {
25+
if (__DEV__) {
26+
const name = (fiber.memoizedProps: ViewTransitionProps).name;
27+
if (name != null && name !== 'auto') {
28+
const existing = mountedNamedViewTransitions.get(name);
29+
if (existing !== undefined) {
30+
if (existing !== fiber && existing !== fiber.alternate) {
31+
if (!didWarnAboutName[name]) {
32+
didWarnAboutName[name] = true;
33+
const stringifiedName = JSON.stringify(name);
34+
runWithFiberInDEV(fiber, () => {
35+
console.error(
36+
'There are two <ViewTransition name=%s> components with the same name mounted ' +
37+
'at the same time. This is not supported and will cause View Transitions ' +
38+
'to error. Try to use a more unique name e.g. by using a namespace prefix ' +
39+
'and adding the id of an item to the name.',
40+
stringifiedName,
41+
);
42+
});
43+
runWithFiberInDEV(existing, () => {
44+
console.error(
45+
'The existing <ViewTransition name=%s> duplicate has this stack trace.',
46+
stringifiedName,
47+
);
48+
});
49+
}
50+
}
51+
} else {
52+
mountedNamedViewTransitions.set(name, fiber);
53+
}
54+
}
55+
}
56+
}
57+
58+
export function untrackNamedViewTransition(fiber: Fiber): void {
59+
if (__DEV__) {
60+
const name = (fiber.memoizedProps: ViewTransitionProps).name;
61+
if (name != null && name !== 'auto') {
62+
const existing = mountedNamedViewTransitions.get(name);
63+
if (
64+
existing !== undefined &&
65+
(existing === fiber || existing === fiber.alternate)
66+
) {
67+
mountedNamedViewTransitions.delete(name);
68+
}
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)