Skip to content

Commit bd7beca

Browse files
committed
Stop a view transition if it gets interrupted before .ready
This ensures that we don't play a partial animation when we're interrupted.
1 parent 5f6ed69 commit bd7beca

File tree

1 file changed

+32
-3
lines changed

1 file changed

+32
-3
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import type {
2121
TransitionAbort,
2222
} from './ReactFiberTracingMarkerComponent';
2323
import type {OffscreenInstance} from './ReactFiberActivityComponent';
24-
import type {Resource, ViewTransitionInstance} from './ReactFiberConfig';
24+
import type {
25+
Resource,
26+
ViewTransitionInstance,
27+
RunningViewTransition,
28+
} from './ReactFiberConfig';
2529
import type {RootState} from './ReactFiberRoot';
2630
import {
2731
getViewTransitionName,
@@ -102,6 +106,7 @@ import {
102106
trackSchedulerEvent,
103107
startViewTransition,
104108
startGestureTransition,
109+
stopViewTransition,
105110
createViewTransitionInstance,
106111
} from './ReactFiberConfig';
107112

@@ -665,6 +670,7 @@ let pendingEffectsRemainingLanes: Lanes = NoLanes;
665670
let pendingEffectsRenderEndTime: number = -0; // Profiling-only
666671
let pendingPassiveTransitions: Array<Transition> | null = null;
667672
let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
673+
let pendingViewTransition: null | RunningViewTransition = null;
668674
let pendingViewTransitionEvents: Array<(types: Array<string>) => void> | null =
669675
null;
670676
let pendingTransitionTypes: null | TransitionTypes = null;
@@ -3504,7 +3510,7 @@ function commitRoot(
35043510

35053511
pendingEffectsStatus = PENDING_MUTATION_PHASE;
35063512
if (enableViewTransition && willStartViewTransition) {
3507-
startViewTransition(
3513+
pendingViewTransition = startViewTransition(
35083514
root.containerInfo,
35093515
pendingTransitionTypes,
35103516
flushMutationEffects,
@@ -3644,6 +3650,8 @@ function flushSpawnedWork(): void {
36443650
}
36453651
pendingEffectsStatus = NO_PENDING_EFFECTS;
36463652

3653+
pendingViewTransition = null; // The view transition has now fully started.
3654+
36473655
// Tell Scheduler to yield at the end of the frame, so the browser has an
36483656
// opportunity to paint.
36493657
requestPaint();
@@ -3913,7 +3921,7 @@ function commitGestureOnRoot(
39133921
pendingTransitionTypes = null;
39143922
pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
39153923

3916-
finishedGesture.running = startGestureTransition(
3924+
pendingViewTransition = finishedGesture.running = startGestureTransition(
39173925
root.containerInfo,
39183926
finishedGesture.provider,
39193927
finishedGesture.rangeCurrent,
@@ -3973,6 +3981,8 @@ function flushGestureAnimations(): void {
39733981
pendingFinishedWork = (null: any); // Clear for GC purposes.
39743982
pendingEffectsLanes = NoLanes;
39753983

3984+
pendingViewTransition = null; // The view transition has now fully started.
3985+
39763986
const prevTransition = ReactSharedInternals.T;
39773987
ReactSharedInternals.T = null;
39783988
const previousPriority = getCurrentUpdatePriority();
@@ -4023,8 +4033,27 @@ function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {
40234033
}
40244034
}
40254035

4036+
let didWarnAboutInterruptedViewTransitions = false;
4037+
40264038
export function flushPendingEffects(wasDelayedCommit?: boolean): boolean {
40274039
// Returns whether passive effects were flushed.
4040+
if (enableViewTransition && pendingViewTransition !== null) {
4041+
// If we forced a flush before the View Transition full started then we skip it.
4042+
// This ensures that we're not running a partial animation.
4043+
stopViewTransition(pendingViewTransition);
4044+
if (__DEV__) {
4045+
if (!didWarnAboutInterruptedViewTransitions) {
4046+
didWarnAboutInterruptedViewTransitions = true;
4047+
console.warn(
4048+
'A flushSync update cancelled a View Transition because it was called ' +
4049+
'while the View Transition was still preparing. To preserve the synchronous ' +
4050+
'semantics, React had to skip the View Transition. If you can, try to avoid ' +
4051+
"flushSync() in a scenario that's likely to interfere.",
4052+
);
4053+
}
4054+
}
4055+
pendingViewTransition = null;
4056+
}
40284057
flushGestureMutations();
40294058
flushGestureAnimations();
40304059
flushMutationEffects();

0 commit comments

Comments
 (0)