Skip to content

Commit b55448b

Browse files
committed
Execute layout phase before after mutation phase inside view transition (#32029)
This allows mutations and scrolling in the layout phase to be counted towards the mutation. This would maybe not be the case for gestures but it is useful for fire-and-forget. This also avoids the issue that if you resolve navigation in useLayoutEffect that it ends up dead locked. It also means that useLayoutEffect does not observe the scroll restoration and in fact, the scroll restoration would win over any manual scrolling in layout effects. For better or worse, this is more in line with how things worked before and how it works in popstate. So it's less of a breaking change. This does mean that we can't unify the after mutation phase with the layout phase though. To do this we need split out flushSpawnedWork from the flushLayoutEffect call. Spawned work from setState inside the layout phase is done outside and not counted towards the transition. They're sync updates and so are not eligible for their own View Transitions. It's also tricky to support this since it's unclear what things like exits in that update would mean. This work will still be able to mutate the live DOM but it's just not eligible to trigger new transitions or adjust the target of those. One difference between popstate is that this spawned work is after scroll restoration. So any scrolling spawned from a second pass would now win over scroll restoration. Another consequence of this change is that you can't safely animate pseudo elements in useLayoutEffect. We'll introduce a better event for that anyway. DiffTrain build for [fd9cfa4](fd9cfa4)
1 parent 2f5045c commit b55448b

35 files changed

+2225
-2045
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
800c9db22e69680f17e238724478537282215f89
1+
fd9cfa416f7c01ecdf76b10ab776a43f2430786d
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
800c9db22e69680f17e238724478537282215f89
1+
fd9cfa416f7c01ecdf76b10ab776a43f2430786d

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1944,7 +1944,7 @@ __DEV__ &&
19441944
exports.useTransition = function () {
19451945
return resolveDispatcher().useTransition();
19461946
};
1947-
exports.version = "19.1.0-www-classic-800c9db2-20250108";
1947+
exports.version = "19.1.0-www-classic-fd9cfa41-20250108";
19481948
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
19491949
"function" ===
19501950
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
@@ -1944,7 +1944,7 @@ __DEV__ &&
19441944
exports.useTransition = function () {
19451945
return resolveDispatcher().useTransition();
19461946
};
1947-
exports.version = "19.1.0-www-modern-800c9db2-20250108";
1947+
exports.version = "19.1.0-www-modern-fd9cfa41-20250108";
19481948
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
19491949
"function" ===
19501950
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
@@ -630,4 +630,4 @@ exports.useSyncExternalStore = function (
630630
exports.useTransition = function () {
631631
return ReactSharedInternals.H.useTransition();
632632
};
633-
exports.version = "19.1.0-www-classic-800c9db2-20250108";
633+
exports.version = "19.1.0-www-classic-fd9cfa41-20250108";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,4 @@ exports.useSyncExternalStore = function (
630630
exports.useTransition = function () {
631631
return ReactSharedInternals.H.useTransition();
632632
};
633-
exports.version = "19.1.0-www-modern-800c9db2-20250108";
633+
exports.version = "19.1.0-www-modern-fd9cfa41-20250108";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ exports.useSyncExternalStore = function (
634634
exports.useTransition = function () {
635635
return ReactSharedInternals.H.useTransition();
636636
};
637-
exports.version = "19.1.0-www-classic-800c9db2-20250108";
637+
exports.version = "19.1.0-www-classic-fd9cfa41-20250108";
638638
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
639639
"function" ===
640640
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
@@ -634,7 +634,7 @@ exports.useSyncExternalStore = function (
634634
exports.useTransition = function () {
635635
return ReactSharedInternals.H.useTransition();
636636
};
637-
exports.version = "19.1.0-www-modern-800c9db2-20250108";
637+
exports.version = "19.1.0-www-modern-fd9cfa41-20250108";
638638
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
639639
"function" ===
640640
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

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

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13387,8 +13387,8 @@ __DEV__ &&
1338713387
}
1338813388
pendingEffectsStatus = PENDING_MUTATION_PHASE;
1338913389
flushMutationEffects();
13390-
pendingEffectsStatus = PENDING_LAYOUT_PHASE;
1339113390
flushLayoutEffects();
13391+
flushSpawnedWork();
1339213392
}
1339313393
}
1339413394
function flushMutationEffects() {
@@ -13420,20 +13420,15 @@ __DEV__ &&
1342013420
}
1342113421
}
1342213422
root.current = finishedWork;
13423-
pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
13423+
pendingEffectsStatus = PENDING_LAYOUT_PHASE;
1342413424
}
1342513425
}
1342613426
function flushLayoutEffects() {
13427-
if (
13428-
pendingEffectsStatus === PENDING_LAYOUT_PHASE ||
13429-
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
13430-
) {
13427+
if (pendingEffectsStatus === PENDING_LAYOUT_PHASE) {
1343113428
pendingEffectsStatus = NO_PENDING_EFFECTS;
1343213429
var root = pendingEffectsRoot,
1343313430
finishedWork = pendingFinishedWork,
1343413431
lanes = pendingEffectsLanes,
13435-
recoverableErrors = pendingRecoverableErrors,
13436-
didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate,
1343713432
rootHasLayoutEffect = 0 !== (finishedWork.flags & 8772);
1343813433
if (0 !== (finishedWork.subtreeFlags & 8772) || rootHasLayoutEffect) {
1343913434
rootHasLayoutEffect = ReactSharedInternals.T;
@@ -13469,29 +13464,43 @@ __DEV__ &&
1346913464
(ReactSharedInternals.T = rootHasLayoutEffect);
1347013465
}
1347113466
}
13467+
pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
13468+
}
13469+
}
13470+
function flushSpawnedWork() {
13471+
if (
13472+
pendingEffectsStatus === PENDING_SPAWNED_WORK ||
13473+
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
13474+
) {
13475+
pendingEffectsStatus = NO_PENDING_EFFECTS;
1347213476
requestPaint();
13473-
(rootHasLayoutEffect =
13474-
0 !== (finishedWork.subtreeFlags & 10256) ||
13475-
0 !== (finishedWork.flags & 10256))
13477+
var root = pendingEffectsRoot,
13478+
finishedWork = pendingFinishedWork,
13479+
lanes = pendingEffectsLanes,
13480+
recoverableErrors = pendingRecoverableErrors,
13481+
didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate,
13482+
rootDidHavePassiveEffects =
13483+
0 !== (finishedWork.subtreeFlags & 10256) ||
13484+
0 !== (finishedWork.flags & 10256);
13485+
rootDidHavePassiveEffects
1347613486
? (pendingEffectsStatus = PENDING_PASSIVE_PHASE)
1347713487
: ((pendingEffectsStatus = NO_PENDING_EFFECTS),
1347813488
(pendingEffectsRoot = null),
1347913489
releaseRootPooledCache(root, root.pendingLanes),
1348013490
(nestedPassiveUpdateCount = 0),
1348113491
(rootWithPassiveNestedUpdates = null));
13482-
previousPriority = root.pendingLanes;
13483-
0 === previousPriority &&
13484-
(legacyErrorBoundariesThatAlreadyFailed = null);
13485-
rootHasLayoutEffect || commitDoubleInvokeEffectsInDEV(root);
13486-
rootHasLayoutEffect = lanesToEventPriority(lanes);
13492+
var remainingLanes = root.pendingLanes;
13493+
0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null);
13494+
rootDidHavePassiveEffects || commitDoubleInvokeEffectsInDEV(root);
13495+
rootDidHavePassiveEffects = lanesToEventPriority(lanes);
1348713496
finishedWork = finishedWork.stateNode;
1348813497
if (
1348913498
injectedHook &&
1349013499
"function" === typeof injectedHook.onCommitFiberRoot
1349113500
)
1349213501
try {
1349313502
var didError = 128 === (finishedWork.current.flags & 128);
13494-
switch (rootHasLayoutEffect) {
13503+
switch (rootDidHavePassiveEffects) {
1349513504
case DiscreteEventPriority:
1349613505
var schedulerPriority = ImmediatePriority;
1349713506
break;
@@ -13550,10 +13559,10 @@ __DEV__ &&
1355013559
}
1355113560
0 !== (pendingEffectsLanes & 3) && flushPendingEffects();
1355213561
ensureRootIsScheduled(root);
13553-
previousPriority = root.pendingLanes;
13562+
remainingLanes = root.pendingLanes;
1355413563
(enableInfiniteRenderLoopDetection &&
1355513564
(didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)) ||
13556-
(0 !== (lanes & 4194218) && 0 !== (previousPriority & 42))
13565+
(0 !== (lanes & 4194218) && 0 !== (remainingLanes & 42))
1355713566
? ((nestedUpdateScheduled = !0),
1355813567
root === rootWithNestedUpdates
1355913568
? nestedUpdateCount++
@@ -13583,6 +13592,7 @@ __DEV__ &&
1358313592
function flushPendingEffects(wasDelayedCommit) {
1358413593
flushMutationEffects();
1358513594
flushLayoutEffects();
13595+
flushSpawnedWork();
1358613596
return flushPassiveEffects(wasDelayedCommit);
1358713597
}
1358813598
function flushPassiveEffects(wasDelayedCommit) {
@@ -16692,9 +16702,10 @@ __DEV__ &&
1669216702
THROTTLED_COMMIT = 2,
1669316703
NO_PENDING_EFFECTS = 0,
1669416704
PENDING_MUTATION_PHASE = 1,
16695-
PENDING_AFTER_MUTATION_PHASE = 2,
16696-
PENDING_LAYOUT_PHASE = 3,
16697-
PENDING_PASSIVE_PHASE = 4,
16705+
PENDING_LAYOUT_PHASE = 2,
16706+
PENDING_AFTER_MUTATION_PHASE = 3,
16707+
PENDING_SPAWNED_WORK = 4,
16708+
PENDING_PASSIVE_PHASE = 5,
1669816709
pendingEffectsStatus = 0,
1669916710
pendingEffectsRoot = null,
1670016711
pendingFinishedWork = null,
@@ -16935,10 +16946,10 @@ __DEV__ &&
1693516946
(function () {
1693616947
var internals = {
1693716948
bundleType: 1,
16938-
version: "19.1.0-www-classic-800c9db2-20250108",
16949+
version: "19.1.0-www-classic-fd9cfa41-20250108",
1693916950
rendererPackageName: "react-art",
1694016951
currentDispatcherRef: ReactSharedInternals,
16941-
reconcilerVersion: "19.1.0-www-classic-800c9db2-20250108"
16952+
reconcilerVersion: "19.1.0-www-classic-fd9cfa41-20250108"
1694216953
};
1694316954
internals.overrideHookState = overrideHookState;
1694416955
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -16972,7 +16983,7 @@ __DEV__ &&
1697216983
exports.Shape = Shape;
1697316984
exports.Surface = Surface;
1697416985
exports.Text = Text;
16975-
exports.version = "19.1.0-www-classic-800c9db2-20250108";
16986+
exports.version = "19.1.0-www-classic-fd9cfa41-20250108";
1697616987
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1697716988
"function" ===
1697816989
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

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

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13201,8 +13201,8 @@ __DEV__ &&
1320113201
}
1320213202
pendingEffectsStatus = PENDING_MUTATION_PHASE;
1320313203
flushMutationEffects();
13204-
pendingEffectsStatus = PENDING_LAYOUT_PHASE;
1320513204
flushLayoutEffects();
13205+
flushSpawnedWork();
1320613206
}
1320713207
}
1320813208
function flushMutationEffects() {
@@ -13234,20 +13234,15 @@ __DEV__ &&
1323413234
}
1323513235
}
1323613236
root.current = finishedWork;
13237-
pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
13237+
pendingEffectsStatus = PENDING_LAYOUT_PHASE;
1323813238
}
1323913239
}
1324013240
function flushLayoutEffects() {
13241-
if (
13242-
pendingEffectsStatus === PENDING_LAYOUT_PHASE ||
13243-
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
13244-
) {
13241+
if (pendingEffectsStatus === PENDING_LAYOUT_PHASE) {
1324513242
pendingEffectsStatus = NO_PENDING_EFFECTS;
1324613243
var root = pendingEffectsRoot,
1324713244
finishedWork = pendingFinishedWork,
1324813245
lanes = pendingEffectsLanes,
13249-
recoverableErrors = pendingRecoverableErrors,
13250-
didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate,
1325113246
rootHasLayoutEffect = 0 !== (finishedWork.flags & 8772);
1325213247
if (0 !== (finishedWork.subtreeFlags & 8772) || rootHasLayoutEffect) {
1325313248
rootHasLayoutEffect = ReactSharedInternals.T;
@@ -13283,29 +13278,43 @@ __DEV__ &&
1328313278
(ReactSharedInternals.T = rootHasLayoutEffect);
1328413279
}
1328513280
}
13281+
pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
13282+
}
13283+
}
13284+
function flushSpawnedWork() {
13285+
if (
13286+
pendingEffectsStatus === PENDING_SPAWNED_WORK ||
13287+
pendingEffectsStatus === PENDING_AFTER_MUTATION_PHASE
13288+
) {
13289+
pendingEffectsStatus = NO_PENDING_EFFECTS;
1328613290
requestPaint();
13287-
(rootHasLayoutEffect =
13288-
0 !== (finishedWork.subtreeFlags & 10256) ||
13289-
0 !== (finishedWork.flags & 10256))
13291+
var root = pendingEffectsRoot,
13292+
finishedWork = pendingFinishedWork,
13293+
lanes = pendingEffectsLanes,
13294+
recoverableErrors = pendingRecoverableErrors,
13295+
didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate,
13296+
rootDidHavePassiveEffects =
13297+
0 !== (finishedWork.subtreeFlags & 10256) ||
13298+
0 !== (finishedWork.flags & 10256);
13299+
rootDidHavePassiveEffects
1329013300
? (pendingEffectsStatus = PENDING_PASSIVE_PHASE)
1329113301
: ((pendingEffectsStatus = NO_PENDING_EFFECTS),
1329213302
(pendingEffectsRoot = null),
1329313303
releaseRootPooledCache(root, root.pendingLanes),
1329413304
(nestedPassiveUpdateCount = 0),
1329513305
(rootWithPassiveNestedUpdates = null));
13296-
previousPriority = root.pendingLanes;
13297-
0 === previousPriority &&
13298-
(legacyErrorBoundariesThatAlreadyFailed = null);
13299-
rootHasLayoutEffect || commitDoubleInvokeEffectsInDEV(root);
13300-
rootHasLayoutEffect = lanesToEventPriority(lanes);
13306+
var remainingLanes = root.pendingLanes;
13307+
0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null);
13308+
rootDidHavePassiveEffects || commitDoubleInvokeEffectsInDEV(root);
13309+
rootDidHavePassiveEffects = lanesToEventPriority(lanes);
1330113310
finishedWork = finishedWork.stateNode;
1330213311
if (
1330313312
injectedHook &&
1330413313
"function" === typeof injectedHook.onCommitFiberRoot
1330513314
)
1330613315
try {
1330713316
var didError = 128 === (finishedWork.current.flags & 128);
13308-
switch (rootHasLayoutEffect) {
13317+
switch (rootDidHavePassiveEffects) {
1330913318
case DiscreteEventPriority:
1331013319
var schedulerPriority = ImmediatePriority;
1331113320
break;
@@ -13364,10 +13373,10 @@ __DEV__ &&
1336413373
}
1336513374
0 !== (pendingEffectsLanes & 3) && flushPendingEffects();
1336613375
ensureRootIsScheduled(root);
13367-
previousPriority = root.pendingLanes;
13376+
remainingLanes = root.pendingLanes;
1336813377
(enableInfiniteRenderLoopDetection &&
1336913378
(didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)) ||
13370-
(0 !== (lanes & 4194218) && 0 !== (previousPriority & 42))
13379+
(0 !== (lanes & 4194218) && 0 !== (remainingLanes & 42))
1337113380
? ((nestedUpdateScheduled = !0),
1337213381
root === rootWithNestedUpdates
1337313382
? nestedUpdateCount++
@@ -13397,6 +13406,7 @@ __DEV__ &&
1339713406
function flushPendingEffects(wasDelayedCommit) {
1339813407
flushMutationEffects();
1339913408
flushLayoutEffects();
13409+
flushSpawnedWork();
1340013410
return flushPassiveEffects(wasDelayedCommit);
1340113411
}
1340213412
function flushPassiveEffects(wasDelayedCommit) {
@@ -16464,9 +16474,10 @@ __DEV__ &&
1646416474
THROTTLED_COMMIT = 2,
1646516475
NO_PENDING_EFFECTS = 0,
1646616476
PENDING_MUTATION_PHASE = 1,
16467-
PENDING_AFTER_MUTATION_PHASE = 2,
16468-
PENDING_LAYOUT_PHASE = 3,
16469-
PENDING_PASSIVE_PHASE = 4,
16477+
PENDING_LAYOUT_PHASE = 2,
16478+
PENDING_AFTER_MUTATION_PHASE = 3,
16479+
PENDING_SPAWNED_WORK = 4,
16480+
PENDING_PASSIVE_PHASE = 5,
1647016481
pendingEffectsStatus = 0,
1647116482
pendingEffectsRoot = null,
1647216483
pendingFinishedWork = null,
@@ -16707,10 +16718,10 @@ __DEV__ &&
1670716718
(function () {
1670816719
var internals = {
1670916720
bundleType: 1,
16710-
version: "19.1.0-www-modern-800c9db2-20250108",
16721+
version: "19.1.0-www-modern-fd9cfa41-20250108",
1671116722
rendererPackageName: "react-art",
1671216723
currentDispatcherRef: ReactSharedInternals,
16713-
reconcilerVersion: "19.1.0-www-modern-800c9db2-20250108"
16724+
reconcilerVersion: "19.1.0-www-modern-fd9cfa41-20250108"
1671416725
};
1671516726
internals.overrideHookState = overrideHookState;
1671616727
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -16744,7 +16755,7 @@ __DEV__ &&
1674416755
exports.Shape = Shape;
1674516756
exports.Surface = Surface;
1674616757
exports.Text = Text;
16747-
exports.version = "19.1.0-www-modern-800c9db2-20250108";
16758+
exports.version = "19.1.0-www-modern-fd9cfa41-20250108";
1674816759
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1674916760
"function" ===
1675016761
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)