Skip to content

Commit c55ffb5

Browse files
authored
Add Clean Up Callbacks to View Transition and Gesture Transition Events (facebook#35564)
Stacked on facebook#35556 and facebook#35559. Given that we don't automatically clean up all view transition animations since facebook#35337 and browsers are buggy, it's important that you clean up any `Animation` started manually from the events. However, there was no clean up function for when the View Transition is forced to stop. This also makes it harder to clean up custom timers etc too. This lets you return a clean up function from all the events on `<ViewTransition>`.
1 parent a49952b commit c55ffb5

File tree

10 files changed

+105
-27
lines changed

10 files changed

+105
-27
lines changed

fixtures/view-transition/src/components/Page.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,12 @@ export default function Page({url, navigate}) {
8282
{rotate: '0deg', transformOrigin: '30px 8px'},
8383
{rotate: '360deg', transformOrigin: '30px 8px'},
8484
];
85-
viewTransition.old.animate(keyframes, 250);
86-
viewTransition.new.animate(keyframes, 250);
85+
const animation1 = viewTransition.old.animate(keyframes, 250);
86+
const animation2 = viewTransition.new.animate(keyframes, 250);
87+
return () => {
88+
animation1.cancel();
89+
animation2.cancel();
90+
};
8791
}
8892

8993
function onGestureTransition(
@@ -105,8 +109,12 @@ export default function Page({url, navigate}) {
105109
rangeStart: (reverse ? rangeEnd : rangeStart) + '%',
106110
rangeEnd: (reverse ? rangeStart : rangeEnd) + '%',
107111
};
108-
viewTransition.old.animate(keyframes, options);
109-
viewTransition.new.animate(keyframes, options);
112+
const animation1 = viewTransition.old.animate(keyframes, options);
113+
const animation2 = viewTransition.new.animate(keyframes, options);
114+
return () => {
115+
animation1.cancel();
116+
animation2.cancel();
117+
};
110118
} else {
111119
// Custom Timeline
112120
const options = {
@@ -120,11 +128,10 @@ export default function Page({url, navigate}) {
120128
// Let the custom timeline take control of driving the animations.
121129
const cleanup1 = timeline.animate(animation1);
122130
const cleanup2 = timeline.animate(animation2);
123-
// TODO: Support returning a clean up function from ViewTransition events.
124-
// return () => {
125-
// cleanup1();
126-
// cleanup2();
127-
// };
131+
return () => {
132+
cleanup1();
133+
cleanup2();
134+
};
128135
}
129136
}
130137

packages/react-art/src/ReactFiberConfigART.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,13 @@ export function startGestureTransition() {
549549

550550
export function stopViewTransition(transition: RunningViewTransition) {}
551551

552+
export function addViewTransitionFinishedListener(
553+
transition: RunningViewTransition,
554+
callback: () => void,
555+
) {
556+
callback();
557+
}
558+
552559
export type ViewTransitionInstance = null | {name: string, ...};
553560

554561
export function createViewTransitionInstance(

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,6 +2337,7 @@ export function startViewTransition(
23372337

23382338
export type RunningViewTransition = {
23392339
skipTransition(): void,
2340+
finished: Promise<void>,
23402341
...
23412342
};
23422343

@@ -2784,6 +2785,13 @@ export function stopViewTransition(transition: RunningViewTransition) {
27842785
transition.skipTransition();
27852786
}
27862787

2788+
export function addViewTransitionFinishedListener(
2789+
transition: RunningViewTransition,
2790+
callback: () => void,
2791+
) {
2792+
transition.finished.finally(callback);
2793+
}
2794+
27872795
interface ViewTransitionPseudoElementType extends mixin$Animatable {
27882796
_scope: HTMLElement;
27892797
_selector: string;

packages/react-native-renderer/src/ReactFiberConfigNative.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,13 @@ export function startGestureTransition(
713713

714714
export function stopViewTransition(transition: RunningViewTransition) {}
715715

716+
export function addViewTransitionFinishedListener(
717+
transition: RunningViewTransition,
718+
callback: () => void,
719+
) {
720+
callback();
721+
}
722+
716723
export type ViewTransitionInstance = null | {name: string, ...};
717724

718725
export function createViewTransitionInstance(

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
888888

889889
stopViewTransition(transition: RunningViewTransition) {},
890890

891+
addViewTransitionFinishedListener(
892+
transition: RunningViewTransition,
893+
callback: () => void,
894+
) {
895+
callback();
896+
},
897+
891898
createViewTransitionInstance(name: string): ViewTransitionInstance {
892899
return null;
893900
},

packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const startViewTransition = shim;
5454
export type RunningViewTransition = null;
5555
export const startGestureTransition = shim;
5656
export const stopViewTransition = shim;
57+
export const addViewTransitionFinishedListener = shim;
5758
export type ViewTransitionInstance = null | {name: string, ...};
5859
export const createViewTransitionInstance = shim;
5960
export type GestureTimeline = any;

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ import {
120120
startViewTransition,
121121
startGestureTransition,
122122
stopViewTransition,
123+
addViewTransitionFinishedListener,
123124
createViewTransitionInstance,
124125
flushHydrationEvents,
125126
} from './ReactFiberConfig';
@@ -733,8 +734,9 @@ let pendingEffectsRenderEndTime: number = -0; // Profiling-only
733734
let pendingPassiveTransitions: Array<Transition> | null = null;
734735
let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
735736
let pendingViewTransition: null | RunningViewTransition = null;
736-
let pendingViewTransitionEvents: Array<(types: Array<string>) => void> | null =
737-
null;
737+
let pendingViewTransitionEvents: Array<
738+
(types: Array<string>) => void | (() => void),
739+
> | null = null;
738740
let pendingTransitionTypes: null | TransitionTypes = null;
739741
let pendingDidIncludeRenderPhaseUpdate: boolean = false;
740742
let pendingSuspendedCommitReason: SuspendedCommitReason = null; // Profiling-only
@@ -899,7 +901,10 @@ export function requestDeferredLane(): Lane {
899901

900902
export function scheduleViewTransitionEvent(
901903
fiber: Fiber,
902-
callback: ?(instance: ViewTransitionInstance, types: Array<string>) => void,
904+
callback: ?(
905+
instance: ViewTransitionInstance,
906+
types: Array<string>,
907+
) => void | (() => void),
903908
): void {
904909
if (enableViewTransition) {
905910
if (callback != null) {
@@ -925,7 +930,7 @@ export function scheduleGestureTransitionEvent(
925930
options: GestureOptionsRequired,
926931
instance: ViewTransitionInstance,
927932
types: Array<string>,
928-
) => void,
933+
) => void | (() => void),
929934
): void {
930935
if (enableGestureTransition) {
931936
if (callback != null) {
@@ -4143,6 +4148,7 @@ function flushSpawnedWork(): void {
41434148

41444149
pendingEffectsStatus = NO_PENDING_EFFECTS;
41454150

4151+
const committedViewTransition = pendingViewTransition;
41464152
pendingViewTransition = null; // The view transition has now fully started.
41474153

41484154
// Tell Scheduler to yield at the end of the frame, so the browser has an
@@ -4262,9 +4268,14 @@ function flushSpawnedWork(): void {
42624268
// Normalize the type. This is lazily created only for events.
42634269
pendingTypes = [];
42644270
}
4265-
for (let i = 0; i < pendingEvents.length; i++) {
4266-
const viewTransitionEvent = pendingEvents[i];
4267-
viewTransitionEvent(pendingTypes);
4271+
if (committedViewTransition !== null) {
4272+
for (let i = 0; i < pendingEvents.length; i++) {
4273+
const viewTransitionEvent = pendingEvents[i];
4274+
const cleanup = viewTransitionEvent(pendingTypes);
4275+
if (cleanup !== undefined) {
4276+
addViewTransitionFinishedListener(committedViewTransition, cleanup);
4277+
}
4278+
}
42684279
}
42694280
}
42704281
}
@@ -4532,9 +4543,18 @@ function flushGestureAnimations(): void {
45324543
// Normalize the type. This is lazily created only for events.
45334544
pendingTypes = [];
45344545
}
4535-
for (let i = 0; i < pendingEvents.length; i++) {
4536-
const viewTransitionEvent = pendingEvents[i];
4537-
viewTransitionEvent(pendingTypes);
4546+
const appliedGesture = root.pendingGestures;
4547+
if (appliedGesture !== null) {
4548+
const runningTransition = appliedGesture.running;
4549+
if (runningTransition !== null) {
4550+
for (let i = 0; i < pendingEvents.length; i++) {
4551+
const viewTransitionEvent = pendingEvents[i];
4552+
const cleanup = viewTransitionEvent(pendingTypes);
4553+
if (cleanup !== undefined) {
4554+
addViewTransitionFinishedListener(runningTransition, cleanup);
4555+
}
4556+
}
4557+
}
45384558
}
45394559
}
45404560
}

packages/react-reconciler/src/forks/ReactFiberConfig.custom.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ export const hasInstanceAffectedParent = $$$config.hasInstanceAffectedParent;
162162
export const startViewTransition = $$$config.startViewTransition;
163163
export const startGestureTransition = $$$config.startGestureTransition;
164164
export const stopViewTransition = $$$config.stopViewTransition;
165+
export const addViewTransitionFinishedListener =
166+
$$$config.addViewTransitionFinishedListener;
165167
export const getCurrentGestureOffset = $$$config.getCurrentGestureOffset;
166168
export const createViewTransitionInstance =
167169
$$$config.createViewTransitionInstance;

packages/react-test-renderer/src/ReactFiberConfigTestHost.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,13 @@ export function startGestureTransition(
459459

460460
export function stopViewTransition(transition: RunningViewTransition) {}
461461

462+
export function addViewTransitionFinishedListener(
463+
transition: RunningViewTransition,
464+
callback: () => void,
465+
) {
466+
callback();
467+
}
468+
462469
export type ViewTransitionInstance = null | {name: string, ...};
463470

464471
export function createViewTransitionInstance(

packages/shared/ReactTypes.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,34 +303,46 @@ export type ViewTransitionProps = {
303303
exit?: ViewTransitionClass,
304304
share?: ViewTransitionClass,
305305
update?: ViewTransitionClass,
306-
onEnter?: (instance: ViewTransitionInstance, types: Array<string>) => void,
307-
onExit?: (instance: ViewTransitionInstance, types: Array<string>) => void,
308-
onShare?: (instance: ViewTransitionInstance, types: Array<string>) => void,
309-
onUpdate?: (instance: ViewTransitionInstance, types: Array<string>) => void,
306+
onEnter?: (
307+
instance: ViewTransitionInstance,
308+
types: Array<string>,
309+
) => void | (() => void),
310+
onExit?: (
311+
instance: ViewTransitionInstance,
312+
types: Array<string>,
313+
) => void | (() => void),
314+
onShare?: (
315+
instance: ViewTransitionInstance,
316+
types: Array<string>,
317+
) => void | (() => void),
318+
onUpdate?: (
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
) => void | (() => void),
310322
onGestureEnter?: (
311323
timeline: GestureProvider,
312324
options: GestureOptionsRequired,
313325
instance: ViewTransitionInstance,
314326
types: Array<string>,
315-
) => void,
327+
) => void | (() => void),
316328
onGestureExit?: (
317329
timeline: GestureProvider,
318330
options: GestureOptionsRequired,
319331
instance: ViewTransitionInstance,
320332
types: Array<string>,
321-
) => void,
333+
) => void | (() => void),
322334
onGestureShare?: (
323335
timeline: GestureProvider,
324336
options: GestureOptionsRequired,
325337
instance: ViewTransitionInstance,
326338
types: Array<string>,
327-
) => void,
339+
) => void | (() => void),
328340
onGestureUpdate?: (
329341
timeline: GestureProvider,
330342
options: GestureOptionsRequired,
331343
instance: ViewTransitionInstance,
332344
types: Array<string>,
333-
) => void,
345+
) => void | (() => void),
334346
};
335347

336348
export type ActivityProps = {

0 commit comments

Comments
 (0)