Make xViewTransitionToHostInstances helpers reusable (#32611)
This prepares from being able to reuse some this in ApplyGesture. These all start with resetting a counter but it's tricky to have to remember to do this and tricky to do from the outside of this module. So we make an exported helper that does the resetting. Ideally it gets inlined. We also stop passing "current" to measureViewTransitionHostInstances. Same thing for cancelViewTransitionHostInstances. This doesn't make sense for "nested" which has not updated and so might not have an alternate. Instead we pass in the old and new name if they might be different.
Sebastian Markbåge committed
Mar 14, 2025 at 13:16 UTC
6daef4e7c8eea99c1b7eb7e451e028c6d7358321
2 files changed
+110
-67
packages/react-reconciler/src/ReactFiberCommitViewTransitions.js
+103
-60
@@ -71,15 +71,40 @@ export let viewTransitionCancelableChildren: null | Array<
71
Instance | string | Props,
72
> = null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props]
73
74
-export function setViewTransitionCancelableChildren(
75
- children: null | Array<Instance | string | Props>,
74
+export function pushViewTransitionCancelableScope(): null | Array<
75
+ Instance | string | Props,
76
+> {
77
+ const prevChildren = viewTransitionCancelableChildren;
78
+ viewTransitionCancelableChildren = null;
79
+ return prevChildren;
80
+}
81
+
82
+export function popViewTransitionCancelableScope(
83
+ prevChildren: null | Array<Instance | string | Props>,
84
): void {
77
- viewTransitionCancelableChildren = children;
85
+ viewTransitionCancelableChildren = prevChildren;
86
}
87
88
let viewTransitionHostInstanceIdx = 0;
89
82
-function applyViewTransitionToHostInstances(
90
+export function applyViewTransitionToHostInstances(
91
+ child: null | Fiber,
92
+ name: string,
93
+ className: ?string,
94
+ collectMeasurements: null | Array<InstanceMeasurement>,
95
+ stopAtNestedViewTransitions: boolean,
96
+): boolean {
97
+ viewTransitionHostInstanceIdx = 0;
98
+ return applyViewTransitionToHostInstancesRecursive(
99
+ child,
100
+ name,
101
+ className,
102
+ collectMeasurements,
103
+ stopAtNestedViewTransitions,
104
+ );
105
+}
106
+
107
+function applyViewTransitionToHostInstancesRecursive(
108
child: null | Fiber,
109
name: string,
110
className: ?string,
@@ -128,7 +153,7 @@ function applyViewTransitionToHostInstances(
153
// inner most one is the one that handles the update.
154
} else {
155
if (
131
- applyViewTransitionToHostInstances(
156
+ applyViewTransitionToHostInstancesRecursive(
157
child.child,
158
name,
159
className,
@@ -207,7 +232,6 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
232
if (className !== 'none') {
233
// We found a new appearing view transition with the same name as this deletion.
234
// We'll transition between them.
210
- viewTransitionHostInstanceIdx = 0;
235
const inViewport = applyViewTransitionToHostInstances(
236
child.child,
237
name,
@@ -242,7 +266,6 @@ export function commitEnterViewTransitions(placement: Fiber): void {
266
state.paired ? props.share : props.enter,
267
);
268
if (className !== 'none') {
245
- viewTransitionHostInstanceIdx = 0;
269
const inViewport = applyViewTransitionToHostInstances(
270
placement.child,
271
name,
@@ -310,7 +333,6 @@ function commitDeletedPairViewTransitions(deletion: Fiber): void {
333
);
334
if (className !== 'none') {
335
// We found a new appearing view transition with the same name as this deletion.
313
- viewTransitionHostInstanceIdx = 0;
336
const inViewport = applyViewTransitionToHostInstances(
337
child.child,
338
name,
@@ -361,7 +383,6 @@ export function commitExitViewTransitions(deletion: Fiber): void {
383
pair !== undefined ? props.share : props.exit,
384
);
385
if (className !== 'none') {
364
- viewTransitionHostInstanceIdx = 0;
386
const inViewport = applyViewTransitionToHostInstances(
387
deletion.child,
388
name,
@@ -449,7 +470,6 @@ export function commitBeforeUpdateViewTransition(
470
return;
471
}
472
}
452
- viewTransitionHostInstanceIdx = 0;
473
applyViewTransitionToHostInstances(
474
current.child,
475
oldName,
@@ -472,7 +492,6 @@ export function commitNestedViewTransitions(changedParent: Fiber): void {
492
props.layout,
493
);
494
if (className !== 'none') {
475
- viewTransitionHostInstanceIdx = 0;
495
applyViewTransitionToHostInstances(
496
child.child,
497
name,
@@ -553,9 +572,22 @@ export function restoreNestedViewTransitions(changedParent: Fiber): void {
572
}
573
}
574
556
-function cancelViewTransitionHostInstances(
557
- currentViewTransition: Fiber,
575
+export function cancelViewTransitionHostInstances(
576
child: null | Fiber,
577
+ oldName: string,
578
+ stopAtNestedViewTransitions: boolean,
579
+): void {
580
+ viewTransitionHostInstanceIdx = 0;
581
+ cancelViewTransitionHostInstancesRecursive(
582
+ child,
583
+ oldName,
584
+ stopAtNestedViewTransitions,
585
+ );
586
+}
587
+
588
+function cancelViewTransitionHostInstancesRecursive(
589
+ child: null | Fiber,
590
+ oldName: string,
591
stopAtNestedViewTransitions: boolean,
592
): void {
593
if (!supportsMutation) {
@@ -564,10 +596,6 @@ function cancelViewTransitionHostInstances(
596
while (child !== null) {
597
if (child.tag === HostComponent) {
598
const instance: Instance = child.stateNode;
567
- const oldName = getViewTransitionName(
568
- currentViewTransition.memoizedProps,
569
- currentViewTransition.stateNode,
570
- );
599
if (viewTransitionCancelableChildren === null) {
600
viewTransitionCancelableChildren = [];
601
}
@@ -589,9 +617,9 @@ function cancelViewTransitionHostInstances(
617
// Skip any nested view transitions for updates since in that case the
618
// inner most one is the one that handles the update.
619
} else {
592
- cancelViewTransitionHostInstances(
593
- currentViewTransition,
620
+ cancelViewTransitionHostInstancesRecursive(
621
child.child,
622
+ oldName,
623
stopAtNestedViewTransitions,
624
);
625
}
@@ -599,11 +627,32 @@ function cancelViewTransitionHostInstances(
627
}
628
}
629
602
-function measureViewTransitionHostInstances(
603
- currentViewTransition: Fiber,
630
+export function measureViewTransitionHostInstances(
631
parentViewTransition: Fiber,
632
child: null | Fiber,
606
- name: string,
633
+ newName: string,
634
+ oldName: string,
635
+ className: ?string,
636
+ previousMeasurements: null | Array<InstanceMeasurement>,
637
+ stopAtNestedViewTransitions: boolean,
638
+): boolean {
639
+ viewTransitionHostInstanceIdx = 0;
640
+ return measureViewTransitionHostInstancesRecursive(
641
+ parentViewTransition,
642
+ child,
643
+ newName,
644
+ oldName,
645
+ className,
646
+ previousMeasurements,
647
+ stopAtNestedViewTransitions,
648
+ );
649
+}
650
+
651
+function measureViewTransitionHostInstancesRecursive(
652
+ parentViewTransition: Fiber,
653
+ child: null | Fiber,
654
+ newName: string,
655
+ oldName: string,
656
className: ?string,
657
previousMeasurements: null | Array<InstanceMeasurement>,
658
stopAtNestedViewTransitions: boolean,
@@ -654,10 +703,10 @@ function measureViewTransitionHostInstances(
703
applyViewTransitionName(
704
instance,
705
viewTransitionHostInstanceIdx === 0
657
- ? name
706
+ ? newName
707
: // If we have multiple Host Instances below, we add a suffix to the name to give
708
// each one a unique name.
660
- name + '_' + viewTransitionHostInstanceIdx,
709
+ newName + '_' + viewTransitionHostInstanceIdx,
710
className,
711
);
712
}
@@ -667,10 +716,6 @@ function measureViewTransitionHostInstances(
716
// animating it. However, in the current model this only works if the parent also
717
// doesn't animate. So we have to queue these and wait until we complete the parent
718
// to cancel them.
670
- const oldName = getViewTransitionName(
671
- currentViewTransition.memoizedProps,
672
- currentViewTransition.stateNode,
673
- );
719
if (viewTransitionCancelableChildren === null) {
720
viewTransitionCancelableChildren = [];
721
}
@@ -696,11 +741,11 @@ function measureViewTransitionHostInstances(
741
parentViewTransition.flags |= child.flags & AffectedParentLayout;
742
} else {
743
if (
699
- measureViewTransitionHostInstances(
700
- currentViewTransition,
744
+ measureViewTransitionHostInstancesRecursive(
745
parentViewTransition,
746
child.child,
703
- name,
747
+ newName,
748
+ oldName,
749
className,
750
previousMeasurements,
751
stopAtNestedViewTransitions,
@@ -719,6 +764,11 @@ export function measureUpdateViewTransition(
764
finishedWork: Fiber,
765
): boolean {
766
const props: ViewTransitionProps = finishedWork.memoizedProps;
767
+ const newName = getViewTransitionName(props, finishedWork.stateNode);
768
+ const oldName = getViewTransitionName(
769
+ current.memoizedProps,
770
+ current.stateNode,
771
+ );
772
const updateClassName: ?string = getViewTransitionClassName(
773
props.className,
774
props.update,
@@ -745,24 +795,21 @@ export function measureUpdateViewTransition(
795
if (layoutClassName === 'none') {
796
// If we did not update, then all changes are considered a layout. We'll
797
// attempt to cancel.
748
- viewTransitionHostInstanceIdx = 0;
749
- cancelViewTransitionHostInstances(current, finishedWork.child, true);
798
+ cancelViewTransitionHostInstances(finishedWork.child, oldName, true);
799
return false;
800
}
801
// We didn't update but we might still apply layout so we measure each
802
// instance to see if it moved or resized.
803
className = layoutClassName;
804
}
756
- const name = getViewTransitionName(props, finishedWork.stateNode);
805
// If nothing changed due to a mutation, or children changing size
806
// and the measurements end up unchanged, we should restore it to not animate.
759
- viewTransitionHostInstanceIdx = 0;
807
const previousMeasurements = current.memoizedState;
808
const inViewport = measureViewTransitionHostInstances(
762
- current,
809
finishedWork,
810
finishedWork.child,
765
- name,
811
+ newName,
812
+ oldName,
813
className,
814
previousMeasurements,
815
true,
@@ -782,29 +829,25 @@ export function measureNestedViewTransitions(changedParent: Fiber): void {
829
let child = changedParent.child;
830
while (child !== null) {
831
if (child.tag === ViewTransitionComponent) {
785
- const current = child.alternate;
786
- if (current !== null) {
787
- const props: ViewTransitionProps = child.memoizedProps;
788
- const name = getViewTransitionName(props, child.stateNode);
789
- const className: ?string = getViewTransitionClassName(
790
- props.className,
791
- props.layout,
792
- );
793
- viewTransitionHostInstanceIdx = 0;
794
- const inViewport = measureViewTransitionHostInstances(
795
- current,
796
- child,
797
- child.child,
798
- name,
799
- className,
800
- child.memoizedState,
801
- false,
802
- );
803
- if ((child.flags & Update) === NoFlags || !inViewport) {
804
- // Nothing changed.
805
- } else {
806
- scheduleViewTransitionEvent(child, props.onLayout);
807
- }
832
+ const props: ViewTransitionProps = child.memoizedProps;
833
+ const name = getViewTransitionName(props, child.stateNode);
834
+ const className: ?string = getViewTransitionClassName(
835
+ props.className,
836
+ props.layout,
837
+ );
838
+ const inViewport = measureViewTransitionHostInstances(
839
+ child,
840
+ child.child,
841
+ name,
842
+ name, // Since this is unchanged, new and old name is the same.
843
+ className,
844
+ child.memoizedState,
845
+ false,
846
+ );
847
+ if ((child.flags & Update) === NoFlags || !inViewport) {
848
+ // Nothing changed.
849
+ } else {
850
+ scheduleViewTransitionEvent(child, props.onLayout);
851
}
852
} else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
853
measureNestedViewTransitions(child);
packages/react-reconciler/src/ReactFiberCommitWork.js
+7
-7
@@ -254,7 +254,8 @@ import {
254
resetAppearingViewTransitions,
255
trackAppearingViewTransition,
256
viewTransitionCancelableChildren,
257
- setViewTransitionCancelableChildren,
257
+ pushViewTransitionCancelableScope,
258
+ popViewTransitionCancelableScope,
259
} from './ReactFiberCommitViewTransitions';
260
import {
261
viewTransitionMutationContext,
@@ -2474,14 +2475,14 @@ function commitAfterMutationEffectsOnFiber(
2475
switch (finishedWork.tag) {
2476
case HostRoot: {
2477
viewTransitionContextChanged = false;
2477
- setViewTransitionCancelableChildren(null);
2478
+ pushViewTransitionCancelableScope();
2479
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2480
if (!viewTransitionContextChanged) {
2481
// If we didn't leak any resizing out to the root, we don't have to transition
2482
// the root itself. This means that we can now safely cancel any cancellations
2483
// that bubbled all the way up.
2484
const cancelableChildren = viewTransitionCancelableChildren;
2484
- setViewTransitionCancelableChildren(null);
2485
+ popViewTransitionCancelableScope(null);
2486
if (cancelableChildren !== null) {
2487
for (let i = 0; i < cancelableChildren.length; i += 3) {
2488
cancelViewTransitionName(
@@ -2532,9 +2533,8 @@ function commitAfterMutationEffectsOnFiber(
2533
const wasMutated = (finishedWork.flags & Update) !== NoFlags;
2534
2535
const prevContextChanged = viewTransitionContextChanged;
2535
- const prevCancelableChildren = viewTransitionCancelableChildren;
2536
+ const prevCancelableChildren = pushViewTransitionCancelableScope();
2537
viewTransitionContextChanged = false;
2537
- setViewTransitionCancelableChildren(null);
2538
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2539
2540
if (viewTransitionContextChanged) {
@@ -2557,7 +2557,7 @@ function commitAfterMutationEffectsOnFiber(
2557
prevCancelableChildren,
2558
viewTransitionCancelableChildren,
2559
);
2560
- setViewTransitionCancelableChildren(prevCancelableChildren);
2560
+ popViewTransitionCancelableScope(prevCancelableChildren);
2561
}
2562
// TODO: If this doesn't end up canceled, because a parent animates,
2563
// then we should probably issue an event since this instance is part of it.
@@ -2571,7 +2571,7 @@ function commitAfterMutationEffectsOnFiber(
2571
);
2572
2573
// If this boundary did update, we cannot cancel its children so those are dropped.
2574
- setViewTransitionCancelableChildren(prevCancelableChildren);
2574
+ popViewTransitionCancelableScope(prevCancelableChildren);
2575
}
2576
2577
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {