Skip to content

Commit 78a6542

Browse files
committed
Warn for duplicate ViewTransition names
1 parent 4845e16 commit 78a6542

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)