Skip to content

Commit 7c2bf8c

Browse files
committed
Don't flush synchronous work if we're in the middle of a ViewTransition async sequence (#32760)
Starting a View Transition is an async sequence. Since React can get a sync update in the middle of sequence we sometimes interrupt that sequence. Currently, we don't actually cancel the View Transition so it can just run as a partial. This ensures that we fully skip it when that happens, as well as warn. However, it's very easy to trigger this with just a setState in useLayoutEffect right now. Therefore if we're inside the preparing sequence of a startViewTransition, this delays work that would've normally flushed in a microtask. ~Maybe we want to do the same for Default work already scheduled through a scheduler Task.~ Edit: This was already done. `flushSync` currently will still lead to an interrupted View Transition (with a warning). There's a tradeoff here whether we want to try our best to preserve the guarantees of `flushSync` or favor the animation. It's already possible to suspend at the root with `flushSync` which means it's not always 100% guaranteed to commit anyway. We could treat it as suspended. But let's see how much this is a problem in practice. DiffTrain build for [a5297ec](a5297ec)
1 parent 574b2c4 commit 7c2bf8c

35 files changed

+1277
-1102
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
254114616a24e0ed66468570b00d34bfabf9f73b
1+
a5297ece6217f5495cbe38ba58f928b2697b0f99
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
254114616a24e0ed66468570b00d34bfabf9f73b
1+
a5297ece6217f5495cbe38ba58f928b2697b0f99

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ __DEV__ &&
15111511
exports.useTransition = function () {
15121512
return resolveDispatcher().useTransition();
15131513
};
1514-
exports.version = "19.1.0-www-classic-25411461-20250326";
1514+
exports.version = "19.1.0-www-classic-a5297ece-20250326";
15151515
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15161516
"function" ===
15171517
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ __DEV__ &&
15111511
exports.useTransition = function () {
15121512
return resolveDispatcher().useTransition();
15131513
};
1514-
exports.version = "19.1.0-www-modern-25411461-20250326";
1514+
exports.version = "19.1.0-www-modern-a5297ece-20250326";
15151515
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15161516
"function" ===
15171517
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,4 +624,4 @@ exports.useSyncExternalStore = function (
624624
exports.useTransition = function () {
625625
return ReactSharedInternals.H.useTransition();
626626
};
627-
exports.version = "19.1.0-www-classic-25411461-20250326";
627+
exports.version = "19.1.0-www-classic-a5297ece-20250326";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,4 +624,4 @@ exports.useSyncExternalStore = function (
624624
exports.useTransition = function () {
625625
return ReactSharedInternals.H.useTransition();
626626
};
627-
exports.version = "19.1.0-www-modern-25411461-20250326";
627+
exports.version = "19.1.0-www-modern-a5297ece-20250326";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ exports.useSyncExternalStore = function (
628628
exports.useTransition = function () {
629629
return ReactSharedInternals.H.useTransition();
630630
};
631-
exports.version = "19.1.0-www-classic-25411461-20250326";
631+
exports.version = "19.1.0-www-classic-a5297ece-20250326";
632632
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
633633
"function" ===
634634
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ exports.useSyncExternalStore = function (
628628
exports.useTransition = function () {
629629
return ReactSharedInternals.H.useTransition();
630630
};
631-
exports.version = "19.1.0-www-modern-25411461-20250326";
631+
exports.version = "19.1.0-www-modern-a5297ece-20250326";
632632
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
633633
"function" ===
634634
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,7 +2661,9 @@ __DEV__ &&
26612661
0 !== (nextLanes & 3) && (mightHavePendingSyncWork = !0));
26622662
root = next;
26632663
}
2664-
flushSyncWorkAcrossRoots_impl(0, !1);
2664+
(pendingEffectsStatus !== NO_PENDING_EFFECTS &&
2665+
pendingEffectsStatus !== PENDING_PASSIVE_PHASE) ||
2666+
flushSyncWorkAcrossRoots_impl(0, !1);
26652667
}
26662668
function scheduleTaskForRootDuringMicrotask(root, currentTime) {
26672669
var pendingLanes = root.pendingLanes,
@@ -10427,9 +10429,10 @@ __DEV__ &&
1042710429
) {
1042810430
for (var inViewport = !1; null !== child; ) {
1042910431
if (5 === child.tag)
10430-
null !== collectMeasurements
10431-
? (collectMeasurements.push(null), (inViewport = !0))
10432-
: inViewport || (inViewport = !0);
10432+
(shouldStartViewTransition = !0),
10433+
null !== collectMeasurements
10434+
? (collectMeasurements.push(null), (inViewport = !0))
10435+
: inViewport || (inViewport = !0);
1043310436
else if (22 !== child.tag || null === child.memoizedState)
1043410437
(30 === child.tag && stopAtNestedViewTransitions) ||
1043510438
(applyViewTransitionToHostInstancesRecursive(
@@ -14562,6 +14565,7 @@ __DEV__ &&
1456214565
completedRenderEndTime,
1456314566
commitStartTime
1456414567
));
14568+
shouldStartViewTransition = !1;
1456514569
suspendedCommitReason = 0 !== (finishedWork.flags & 13878);
1456614570
if (
1456714571
0 !== (finishedWork.subtreeFlags & 13878) ||
@@ -14581,10 +14585,11 @@ __DEV__ &&
1458114585
(ReactSharedInternals.T = suspendedCommitReason);
1458214586
}
1458314587
}
14588+
root = shouldStartViewTransition;
1458414589
pendingEffectsStatus = PENDING_MUTATION_PHASE;
14585-
flushMutationEffects();
14586-
flushLayoutEffects();
14587-
flushSpawnedWork();
14590+
enableViewTransition && root
14591+
? (pendingViewTransition = null)
14592+
: (flushMutationEffects(), flushLayoutEffects(), flushSpawnedWork());
1458814593
}
1458914594
}
1459014595
function flushMutationEffects() {
@@ -14671,6 +14676,7 @@ __DEV__ &&
1467114676
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
1467214677
) {
1467314678
pendingEffectsStatus = NO_PENDING_EFFECTS;
14679+
pendingViewTransition = null;
1467414680
requestPaint();
1467514681
var root = pendingEffectsRoot,
1467614682
finishedWork = pendingFinishedWork,
@@ -14830,6 +14836,14 @@ __DEV__ &&
1483014836
((root.pooledCache = null), releaseCache(remainingLanes)));
1483114837
}
1483214838
function flushPendingEffects(wasDelayedCommit) {
14839+
enableViewTransition &&
14840+
null !== pendingViewTransition &&
14841+
(didWarnAboutInterruptedViewTransitions ||
14842+
((didWarnAboutInterruptedViewTransitions = !0),
14843+
console.warn(
14844+
"A flushSync update cancelled a View Transition because it was called while the View Transition was still preparing. To preserve the synchronous semantics, React had to skip the View Transition. If you can, try to avoid flushSync() in a scenario that's likely to interfere."
14845+
)),
14846+
(pendingViewTransition = null));
1483314847
flushMutationEffects();
1483414848
flushLayoutEffects();
1483514849
flushSpawnedWork();
@@ -17826,6 +17840,7 @@ __DEV__ &&
1782617840
didWarnAboutUndefinedSnapshotBeforeUpdate = null;
1782717841
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
1782817842
var viewTransitionMutationContext = !1,
17843+
shouldStartViewTransition = !1,
1782917844
appearingViewTransitions = null,
1783017845
mountedNamedViewTransitions = new Map(),
1783117846
didWarnAboutName = {},
@@ -17928,6 +17943,7 @@ __DEV__ &&
1792817943
pendingEffectsRenderEndTime = -0,
1792917944
pendingPassiveTransitions = null,
1793017945
pendingRecoverableErrors = null,
17946+
pendingViewTransition = null,
1793117947
pendingViewTransitionEvents = null,
1793217948
pendingTransitionTypes = null,
1793317949
pendingDidIncludeRenderPhaseUpdate = !1,
@@ -17941,6 +17957,7 @@ __DEV__ &&
1794117957
nestedPassiveUpdateCount = 0,
1794217958
rootWithPassiveNestedUpdates = null,
1794317959
isRunningInsertionEffect = !1,
17960+
didWarnAboutInterruptedViewTransitions = !1,
1794417961
didWarnStateUpdateForNotYetMountedComponent = null,
1794517962
didWarnAboutUpdateInRender = !1;
1794617963
var didWarnAboutUpdateInRenderForAnotherComponent = new Set();
@@ -18164,10 +18181,10 @@ __DEV__ &&
1816418181
(function () {
1816518182
var internals = {
1816618183
bundleType: 1,
18167-
version: "19.1.0-www-classic-25411461-20250326",
18184+
version: "19.1.0-www-classic-a5297ece-20250326",
1816818185
rendererPackageName: "react-art",
1816918186
currentDispatcherRef: ReactSharedInternals,
18170-
reconcilerVersion: "19.1.0-www-classic-25411461-20250326"
18187+
reconcilerVersion: "19.1.0-www-classic-a5297ece-20250326"
1817118188
};
1817218189
internals.overrideHookState = overrideHookState;
1817318190
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -18201,7 +18218,7 @@ __DEV__ &&
1820118218
exports.Shape = Shape;
1820218219
exports.Surface = Surface;
1820318220
exports.Text = Text;
18204-
exports.version = "19.1.0-www-classic-25411461-20250326";
18221+
exports.version = "19.1.0-www-classic-a5297ece-20250326";
1820518222
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1820618223
"function" ===
1820718224
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2567,7 +2567,9 @@ __DEV__ &&
25672567
0 !== (nextLanes & 3) && (mightHavePendingSyncWork = !0));
25682568
root = next;
25692569
}
2570-
flushSyncWorkAcrossRoots_impl(0, !1);
2570+
(pendingEffectsStatus !== NO_PENDING_EFFECTS &&
2571+
pendingEffectsStatus !== PENDING_PASSIVE_PHASE) ||
2572+
flushSyncWorkAcrossRoots_impl(0, !1);
25712573
}
25722574
function scheduleTaskForRootDuringMicrotask(root, currentTime) {
25732575
var pendingLanes = root.pendingLanes,
@@ -10245,9 +10247,10 @@ __DEV__ &&
1024510247
) {
1024610248
for (var inViewport = !1; null !== child; ) {
1024710249
if (5 === child.tag)
10248-
null !== collectMeasurements
10249-
? (collectMeasurements.push(null), (inViewport = !0))
10250-
: inViewport || (inViewport = !0);
10250+
(shouldStartViewTransition = !0),
10251+
null !== collectMeasurements
10252+
? (collectMeasurements.push(null), (inViewport = !0))
10253+
: inViewport || (inViewport = !0);
1025110254
else if (22 !== child.tag || null === child.memoizedState)
1025210255
(30 === child.tag && stopAtNestedViewTransitions) ||
1025310256
(applyViewTransitionToHostInstancesRecursive(
@@ -14376,6 +14379,7 @@ __DEV__ &&
1437614379
completedRenderEndTime,
1437714380
commitStartTime
1437814381
));
14382+
shouldStartViewTransition = !1;
1437914383
suspendedCommitReason = 0 !== (finishedWork.flags & 13878);
1438014384
if (
1438114385
0 !== (finishedWork.subtreeFlags & 13878) ||
@@ -14395,10 +14399,11 @@ __DEV__ &&
1439514399
(ReactSharedInternals.T = suspendedCommitReason);
1439614400
}
1439714401
}
14402+
root = shouldStartViewTransition;
1439814403
pendingEffectsStatus = PENDING_MUTATION_PHASE;
14399-
flushMutationEffects();
14400-
flushLayoutEffects();
14401-
flushSpawnedWork();
14404+
enableViewTransition && root
14405+
? (pendingViewTransition = null)
14406+
: (flushMutationEffects(), flushLayoutEffects(), flushSpawnedWork());
1440214407
}
1440314408
}
1440414409
function flushMutationEffects() {
@@ -14485,6 +14490,7 @@ __DEV__ &&
1448514490
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
1448614491
) {
1448714492
pendingEffectsStatus = NO_PENDING_EFFECTS;
14493+
pendingViewTransition = null;
1448814494
requestPaint();
1448914495
var root = pendingEffectsRoot,
1449014496
finishedWork = pendingFinishedWork,
@@ -14644,6 +14650,14 @@ __DEV__ &&
1464414650
((root.pooledCache = null), releaseCache(remainingLanes)));
1464514651
}
1464614652
function flushPendingEffects(wasDelayedCommit) {
14653+
enableViewTransition &&
14654+
null !== pendingViewTransition &&
14655+
(didWarnAboutInterruptedViewTransitions ||
14656+
((didWarnAboutInterruptedViewTransitions = !0),
14657+
console.warn(
14658+
"A flushSync update cancelled a View Transition because it was called while the View Transition was still preparing. To preserve the synchronous semantics, React had to skip the View Transition. If you can, try to avoid flushSync() in a scenario that's likely to interfere."
14659+
)),
14660+
(pendingViewTransition = null));
1464714661
flushMutationEffects();
1464814662
flushLayoutEffects();
1464914663
flushSpawnedWork();
@@ -17598,6 +17612,7 @@ __DEV__ &&
1759817612
didWarnAboutUndefinedSnapshotBeforeUpdate = null;
1759917613
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set();
1760017614
var viewTransitionMutationContext = !1,
17615+
shouldStartViewTransition = !1,
1760117616
appearingViewTransitions = null,
1760217617
mountedNamedViewTransitions = new Map(),
1760317618
didWarnAboutName = {},
@@ -17700,6 +17715,7 @@ __DEV__ &&
1770017715
pendingEffectsRenderEndTime = -0,
1770117716
pendingPassiveTransitions = null,
1770217717
pendingRecoverableErrors = null,
17718+
pendingViewTransition = null,
1770317719
pendingViewTransitionEvents = null,
1770417720
pendingTransitionTypes = null,
1770517721
pendingDidIncludeRenderPhaseUpdate = !1,
@@ -17713,6 +17729,7 @@ __DEV__ &&
1771317729
nestedPassiveUpdateCount = 0,
1771417730
rootWithPassiveNestedUpdates = null,
1771517731
isRunningInsertionEffect = !1,
17732+
didWarnAboutInterruptedViewTransitions = !1,
1771617733
didWarnStateUpdateForNotYetMountedComponent = null,
1771717734
didWarnAboutUpdateInRender = !1;
1771817735
var didWarnAboutUpdateInRenderForAnotherComponent = new Set();
@@ -17936,10 +17953,10 @@ __DEV__ &&
1793617953
(function () {
1793717954
var internals = {
1793817955
bundleType: 1,
17939-
version: "19.1.0-www-modern-25411461-20250326",
17956+
version: "19.1.0-www-modern-a5297ece-20250326",
1794017957
rendererPackageName: "react-art",
1794117958
currentDispatcherRef: ReactSharedInternals,
17942-
reconcilerVersion: "19.1.0-www-modern-25411461-20250326"
17959+
reconcilerVersion: "19.1.0-www-modern-a5297ece-20250326"
1794317960
};
1794417961
internals.overrideHookState = overrideHookState;
1794517962
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -17973,7 +17990,7 @@ __DEV__ &&
1797317990
exports.Shape = Shape;
1797417991
exports.Surface = Surface;
1797517992
exports.Text = Text;
17976-
exports.version = "19.1.0-www-modern-25411461-20250326";
17993+
exports.version = "19.1.0-www-modern-a5297ece-20250326";
1797717994
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1797817995
"function" ===
1797917996
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)