[Fiber] Refactor Commit Phase into Separate Functions for Before Mutation/Mutation/Layout (#31930)
This is doing some general clean up to be able to split the commit root three phases into three separate async steps.
Sebastian Markbåge committed
Jan 2, 2025 at 14:55 UTC
c81312e3a78dcbf71ed98c8893abe6dbfeaef3f2
6 files changed
+174
-154
packages/react-reconciler/src/ReactFiberLane.js
+5
-11
@@ -221,7 +221,11 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
221
}
222
}
223
224
-export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
224
+export function getNextLanes(
225
+ root: FiberRoot,
226
+ wipLanes: Lanes,
227
+ rootHasPendingCommit: boolean,
228
+): Lanes {
229
// Early bailout if there's no pending work left.
230
const pendingLanes = root.pendingLanes;
231
if (pendingLanes === NoLanes) {
@@ -246,16 +250,6 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
250
// a brief amount of time (i.e. below the "Just Noticeable Difference"
251
// threshold).
252
//
249
- // TODO: finishedLanes is also set when a Suspensey resource, like CSS or
250
- // images, suspends during the commit phase. (We could detect that here by
251
- // checking for root.cancelPendingCommit.) These are also expected to resolve
252
- // quickly, because of preloading, but theoretically they could block forever
253
- // like in a normal "suspend indefinitely" scenario. In the future, we should
254
- // consider only blocking for up to some time limit before discarding the
255
- // commit in favor of prerendering. If we do discard a pending commit, then
256
- // the commit phase callback should act as a ping to try the original
257
- // render again.
258
- const rootHasPendingCommit = root.finishedLanes !== NoLanes;
253
254
// Do not work on any idle work until all the non-idle work has finished,
255
// even if the work is suspended.
packages/react-reconciler/src/ReactFiberRoot.js
-2
@@ -61,7 +61,6 @@ function FiberRootNode(
61
this.pendingChildren = null;
62
this.current = null;
63
this.pingCache = null;
64
- this.finishedWork = null;
64
this.timeoutHandle = noTimeout;
65
this.cancelPendingCommit = null;
66
this.context = null;
@@ -76,7 +75,6 @@ function FiberRootNode(
75
this.pingedLanes = NoLanes;
76
this.warmLanes = NoLanes;
77
this.expiredLanes = NoLanes;
79
- this.finishedLanes = NoLanes;
78
this.errorRecoveryDisabledLanes = NoLanes;
79
this.shellSuspendCounter = 0;
80
packages/react-reconciler/src/ReactFiberRootScheduler.js
+11
@@ -69,6 +69,7 @@ import {
69
scheduleMicrotask,
70
shouldAttemptEagerTransition,
71
trackSchedulerEvent,
72
+ noTimeout,
73
} from './ReactFiberConfig';
74
75
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -207,11 +208,15 @@ function flushSyncWorkAcrossRoots_impl(
208
const workInProgressRoot = getWorkInProgressRoot();
209
const workInProgressRootRenderLanes =
210
getWorkInProgressRootRenderLanes();
211
+ const rootHasPendingCommit =
212
+ root.cancelPendingCommit !== null ||
213
+ root.timeoutHandle !== noTimeout;
214
const nextLanes = getNextLanes(
215
root,
216
root === workInProgressRoot
217
? workInProgressRootRenderLanes
218
: NoLanes,
219
+ rootHasPendingCommit,
220
);
221
if (
222
includesSyncLane(nextLanes) &&
@@ -335,6 +340,8 @@ function scheduleTaskForRootDuringMicrotask(
340
const pendingPassiveEffectsLanes = getPendingPassiveEffectsLanes();
341
const workInProgressRoot = getWorkInProgressRoot();
342
const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
343
+ const rootHasPendingCommit =
344
+ root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
345
const nextLanes =
346
enableYieldingBeforePassive && root === rootWithPendingPassiveEffects
347
? // This will schedule the callback at the priority of the lane but we used to
@@ -345,6 +352,7 @@ function scheduleTaskForRootDuringMicrotask(
352
: getNextLanes(
353
root,
354
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
355
+ rootHasPendingCommit,
356
);
357
358
const existingCallbackNode = root.callbackNode;
@@ -488,9 +496,12 @@ function performWorkOnRootViaSchedulerTask(
496
// it's available).
497
const workInProgressRoot = getWorkInProgressRoot();
498
const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
499
+ const rootHasPendingCommit =
500
+ root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;
501
const lanes = getNextLanes(
502
root,
503
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
504
+ rootHasPendingCommit,
505
);
506
if (lanes === NoLanes) {
507
// No more work on this root.
packages/react-reconciler/src/ReactFiberWorkLoop.js
+157
-137
@@ -14,7 +14,6 @@ import type {Fiber, FiberRoot} from './ReactInternalTypes';
14
import type {Lanes, Lane} from './ReactFiberLane';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent';
16
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
17
-import type {EventPriority} from './ReactEventPriorities';
17
import type {
18
PendingTransitionCallbacks,
19
PendingBoundaries,
@@ -1240,16 +1239,12 @@ function finishConcurrentRender(
1239
}
1240
}
1241
1243
- // Only set these if we have a complete tree that is ready to be committed.
1244
- // We use these fields to determine later whether or not the work should be
1245
- // discarded for a fresh render attempt.
1246
- root.finishedWork = finishedWork;
1247
- root.finishedLanes = lanes;
1248
-
1242
if (shouldForceFlushFallbacksInDEV()) {
1243
// We're inside an `act` scope. Commit immediately.
1244
commitRoot(
1245
root,
1246
+ finishedWork,
1247
+ lanes,
1248
workInProgressRootRecoverableErrors,
1249
workInProgressTransitions,
1250
workInProgressRootDidIncludeRecursiveRenderUpdate,
@@ -1282,7 +1277,7 @@ function finishConcurrentRender(
1277
didAttemptEntireTree,
1278
);
1279
1285
- const nextLanes = getNextLanes(root, NoLanes);
1280
+ const nextLanes = getNextLanes(root, NoLanes, true);
1281
if (nextLanes !== NoLanes) {
1282
// There's additional work we can do on this root. We might as well
1283
// attempt to work on that while we're suspended.
@@ -1352,6 +1347,8 @@ function commitRootWhenReady(
1347
completedRenderStartTime: number, // Profiling-only
1348
completedRenderEndTime: number, // Profiling-only
1349
) {
1350
+ root.timeoutHandle = noTimeout;
1351
+
1352
// TODO: Combine retry throttling with Suspensey commits. Right now they run
1353
// one after the other.
1354
const BothVisibilityAndMaySuspendCommit = Visibility | MaySuspendCommit;
@@ -1385,6 +1382,8 @@ function commitRootWhenReady(
1382
commitRoot.bind(
1383
null,
1384
root,
1385
+ finishedWork,
1386
+ lanes,
1387
recoverableErrors,
1388
transitions,
1389
didIncludeRenderPhaseUpdate,
@@ -1406,6 +1405,8 @@ function commitRootWhenReady(
1405
// Otherwise, commit immediately.;
1406
commitRoot(
1407
root,
1408
+ finishedWork,
1409
+ lanes,
1410
recoverableErrors,
1411
transitions,
1412
didIncludeRenderPhaseUpdate,
@@ -1843,9 +1844,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
1844
}
1845
}
1846
1846
- root.finishedWork = null;
1847
- root.finishedLanes = NoLanes;
1848
-
1847
const timeoutHandle = root.timeoutHandle;
1848
if (timeoutHandle !== noTimeout) {
1849
// The root previous suspended and scheduled a timeout to commit a fallback
@@ -3129,6 +3127,8 @@ const THROTTLED_COMMIT = 2;
3127
3128
function commitRoot(
3129
root: FiberRoot,
3130
+ finishedWork: null | Fiber,
3131
+ lanes: Lanes,
3132
recoverableErrors: null | Array<CapturedValue<mixed>>,
3133
transitions: Array<Transition> | null,
3134
didIncludeRenderPhaseUpdate: boolean,
@@ -3139,48 +3139,9 @@ function commitRoot(
3139
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3140
completedRenderStartTime: number, // Profiling-only
3141
completedRenderEndTime: number, // Profiling-only
3142
-) {
3143
- // TODO: This no longer makes any sense. We already wrap the mutation and
3144
- // layout phases. Should be able to remove.
3145
- const prevTransition = ReactSharedInternals.T;
3146
- const previousUpdateLanePriority = getCurrentUpdatePriority();
3147
- try {
3148
- setCurrentUpdatePriority(DiscreteEventPriority);
3149
- ReactSharedInternals.T = null;
3150
- commitRootImpl(
3151
- root,
3152
- recoverableErrors,
3153
- transitions,
3154
- didIncludeRenderPhaseUpdate,
3155
- previousUpdateLanePriority,
3156
- spawnedLane,
3157
- updatedLanes,
3158
- suspendedRetryLanes,
3159
- exitStatus,
3160
- suspendedCommitReason,
3161
- completedRenderStartTime,
3162
- completedRenderEndTime,
3163
- );
3164
- } finally {
3165
- ReactSharedInternals.T = prevTransition;
3166
- setCurrentUpdatePriority(previousUpdateLanePriority);
3167
- }
3168
-}
3142
+): void {
3143
+ root.cancelPendingCommit = null;
3144
3170
-function commitRootImpl(
3171
- root: FiberRoot,
3172
- recoverableErrors: null | Array<CapturedValue<mixed>>,
3173
- transitions: Array<Transition> | null,
3174
- didIncludeRenderPhaseUpdate: boolean,
3175
- renderPriorityLevel: EventPriority,
3176
- spawnedLane: Lane,
3177
- updatedLanes: Lanes,
3178
- suspendedRetryLanes: Lanes,
3179
- exitStatus: RootExitStatus, // Profiling-only
3180
- suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3181
- completedRenderStartTime: number, // Profiling-only
3182
- completedRenderEndTime: number, // Profiling-only
3183
-) {
3145
do {
3146
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
3147
// means `flushPassiveEffects` will sometimes result in additional
@@ -3196,9 +3157,6 @@ function commitRootImpl(
3157
throw new Error('Should not already be working.');
3158
}
3159
3199
- const finishedWork = root.finishedWork;
3200
- const lanes = root.finishedLanes;
3201
-
3160
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3161
// Log the previous render phase once we commit. I.e. we weren't interrupted.
3162
setCurrentTrackFromLanes(lanes);
@@ -3234,19 +3192,17 @@ function commitRootImpl(
3192
if (enableSchedulingProfiler) {
3193
markCommitStopped();
3194
}
3237
- return null;
3195
+ return;
3196
} else {
3197
if (__DEV__) {
3198
if (lanes === NoLanes) {
3199
console.error(
3242
- 'root.finishedLanes should not be empty during a commit. This is a ' +
3200
+ 'finishedLanes should not be empty during a commit. This is a ' +
3201
'bug in React.',
3202
);
3203
}
3204
}
3205
}
3248
- root.finishedWork = null;
3249
- root.finishedLanes = NoLanes;
3206
3207
if (finishedWork === root.current) {
3208
throw new Error(
@@ -3292,7 +3248,6 @@ function commitRootImpl(
3248
// might get scheduled in the commit phase. (See #16714.)
3249
// TODO: Delete all other places that schedule the passive effect callback
3250
// They're redundant.
3295
- let rootDoesHavePassiveEffects: boolean = false;
3251
if (
3252
// If this subtree rendered with profiling this commit, we need to visit it to log it.
3253
(enableProfilerTimer &&
@@ -3301,7 +3256,6 @@ function commitRootImpl(
3256
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
3257
(finishedWork.flags & PassiveMask) !== NoFlags
3258
) {
3304
- rootDoesHavePassiveEffects = true;
3259
pendingPassiveEffectsRemainingLanes = remainingLanes;
3260
pendingPassiveEffectsRenderEndTime = completedRenderEndTime;
3261
// workInProgressTransitions might be overwritten, so we want
@@ -3319,7 +3273,6 @@ function commitRootImpl(
3273
// So we can clear these now to allow a new callback to be scheduled.
3274
root.callbackNode = null;
3275
root.callbackPriority = NoLane;
3322
- root.cancelPendingCommit = null;
3276
scheduleCallback(NormalSchedulerPriority, () => {
3277
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3278
// Track the currently executing event if there is one so we can ignore this
@@ -3338,7 +3291,6 @@ function commitRootImpl(
3291
// so we can clear the callback now.
3292
root.callbackNode = null;
3293
root.callbackPriority = NoLane;
3341
- root.cancelPendingCommit = null;
3294
}
3295
3296
if (enableProfilerTimer) {
@@ -3355,79 +3307,136 @@ function commitRootImpl(
3307
}
3308
}
3309
3310
+ // The commit phase is broken into several sub-phases. We do a separate pass
3311
+ // of the effect list for each phase: all mutation effects come before all
3312
+ // layout effects, and so on.
3313
+
3314
// Check if there are any effects in the whole tree.
3315
// TODO: This is left over from the effect list implementation, where we had
3316
// to check for the existence of `firstEffect` to satisfy Flow. I think the
3317
// only other reason this optimization exists is because it affects profiling.
3318
// Reconsider whether this is necessary.
3363
- const subtreeHasEffects =
3364
- (finishedWork.subtreeFlags &
3365
- (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
3366
- NoFlags;
3367
- const rootHasEffect =
3368
- (finishedWork.flags &
3369
- (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
3319
+ const subtreeHasBeforeMutationEffects =
3320
+ (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask)) !==
3321
NoFlags;
3322
+ const rootHasBeforeMutationEffect =
3323
+ (finishedWork.flags & (BeforeMutationMask | MutationMask)) !== NoFlags;
3324
3372
- if (subtreeHasEffects || rootHasEffect) {
3325
+ if (subtreeHasBeforeMutationEffects || rootHasBeforeMutationEffect) {
3326
const prevTransition = ReactSharedInternals.T;
3327
ReactSharedInternals.T = null;
3328
const previousPriority = getCurrentUpdatePriority();
3329
setCurrentUpdatePriority(DiscreteEventPriority);
3377
-
3330
const prevExecutionContext = executionContext;
3331
executionContext |= CommitContext;
3332
+ try {
3333
+ // The first phase a "before mutation" phase. We use this phase to read the
3334
+ // state of the host tree right before we mutate it. This is where
3335
+ // getSnapshotBeforeUpdate is called.
3336
+ commitBeforeMutationEffects(root, finishedWork);
3337
+ } finally {
3338
+ // Reset the priority to the previous non-sync value.
3339
+ executionContext = prevExecutionContext;
3340
+ setCurrentUpdatePriority(previousPriority);
3341
+ ReactSharedInternals.T = prevTransition;
3342
+ }
3343
+ }
3344
+ flushMutationEffects(root, finishedWork, lanes);
3345
+ flushLayoutEffects(
3346
+ root,
3347
+ finishedWork,
3348
+ lanes,
3349
+ recoverableErrors,
3350
+ didIncludeRenderPhaseUpdate,
3351
+ suspendedCommitReason,
3352
+ completedRenderEndTime,
3353
+ );
3354
+}
3355
3381
- // The commit phase is broken into several sub-phases. We do a separate pass
3382
- // of the effect list for each phase: all mutation effects come before all
3383
- // layout effects, and so on.
3384
-
3385
- // The first phase a "before mutation" phase. We use this phase to read the
3386
- // state of the host tree right before we mutate it. This is where
3387
- // getSnapshotBeforeUpdate is called.
3388
- commitBeforeMutationEffects(root, finishedWork);
3356
+function flushMutationEffects(
3357
+ root: FiberRoot,
3358
+ finishedWork: Fiber,
3359
+ lanes: Lanes,
3360
+): void {
3361
+ const subtreeMutationHasEffects =
3362
+ (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
3363
+ const rootMutationHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
3364
3390
- // The next phase is the mutation phase, where we mutate the host tree.
3391
- commitMutationEffects(root, finishedWork, lanes);
3365
+ if (subtreeMutationHasEffects || rootMutationHasEffect) {
3366
+ const prevTransition = ReactSharedInternals.T;
3367
+ ReactSharedInternals.T = null;
3368
+ const previousPriority = getCurrentUpdatePriority();
3369
+ setCurrentUpdatePriority(DiscreteEventPriority);
3370
+ const prevExecutionContext = executionContext;
3371
+ executionContext |= CommitContext;
3372
+ try {
3373
+ // The next phase is the mutation phase, where we mutate the host tree.
3374
+ commitMutationEffects(root, finishedWork, lanes);
3375
3393
- if (enableCreateEventHandleAPI) {
3394
- if (shouldFireAfterActiveInstanceBlur) {
3395
- afterActiveInstanceBlur();
3376
+ if (enableCreateEventHandleAPI) {
3377
+ if (shouldFireAfterActiveInstanceBlur) {
3378
+ afterActiveInstanceBlur();
3379
+ }
3380
}
3381
+ resetAfterCommit(root.containerInfo);
3382
+ } finally {
3383
+ // Reset the priority to the previous non-sync value.
3384
+ executionContext = prevExecutionContext;
3385
+ setCurrentUpdatePriority(previousPriority);
3386
+ ReactSharedInternals.T = prevTransition;
3387
}
3398
- resetAfterCommit(root.containerInfo);
3399
-
3400
- // The work-in-progress tree is now the current tree. This must come after
3401
- // the mutation phase, so that the previous tree is still current during
3402
- // componentWillUnmount, but before the layout phase, so that the finished
3403
- // work is current during componentDidMount/Update.
3404
- root.current = finishedWork;
3405
-
3406
- // The next phase is the layout phase, where we call effects that read
3407
- // the host tree after it's been mutated. The idiomatic use case for this is
3408
- // layout, but class component lifecycles also fire here for legacy reasons.
3409
- if (enableSchedulingProfiler) {
3410
- markLayoutEffectsStarted(lanes);
3411
- }
3412
- commitLayoutEffects(finishedWork, root, lanes);
3413
- if (enableSchedulingProfiler) {
3414
- markLayoutEffectsStopped();
3415
- }
3388
+ }
3389
3417
- // Tell Scheduler to yield at the end of the frame, so the browser has an
3418
- // opportunity to paint.
3419
- requestPaint();
3390
+ // The work-in-progress tree is now the current tree. This must come after
3391
+ // the mutation phase, so that the previous tree is still current during
3392
+ // componentWillUnmount, but before the layout phase, so that the finished
3393
+ // work is current during componentDidMount/Update.
3394
+ root.current = finishedWork;
3395
+}
3396
3421
- executionContext = prevExecutionContext;
3397
+function flushLayoutEffects(
3398
+ root: FiberRoot,
3399
+ finishedWork: Fiber,
3400
+ lanes: Lanes,
3401
+ recoverableErrors: null | Array<CapturedValue<mixed>>,
3402
+ didIncludeRenderPhaseUpdate: boolean,
3403
+ suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3404
+ completedRenderEndTime: number, // Profiling-only
3405
+): void {
3406
+ const subtreeHasLayoutEffects =
3407
+ (finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
3408
+ const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;
3409
3423
- // Reset the priority to the previous non-sync value.
3424
- setCurrentUpdatePriority(previousPriority);
3425
- ReactSharedInternals.T = prevTransition;
3426
- } else {
3427
- // No effects.
3428
- root.current = finishedWork;
3410
+ if (subtreeHasLayoutEffects || rootHasLayoutEffect) {
3411
+ const prevTransition = ReactSharedInternals.T;
3412
+ ReactSharedInternals.T = null;
3413
+ const previousPriority = getCurrentUpdatePriority();
3414
+ setCurrentUpdatePriority(DiscreteEventPriority);
3415
+ const prevExecutionContext = executionContext;
3416
+ executionContext |= CommitContext;
3417
+ try {
3418
+ // The next phase is the layout phase, where we call effects that read
3419
+ // the host tree after it's been mutated. The idiomatic use case for this is
3420
+ // layout, but class component lifecycles also fire here for legacy reasons.
3421
+ if (enableSchedulingProfiler) {
3422
+ markLayoutEffectsStarted(lanes);
3423
+ }
3424
+ commitLayoutEffects(finishedWork, root, lanes);
3425
+ if (enableSchedulingProfiler) {
3426
+ markLayoutEffectsStopped();
3427
+ }
3428
+ } finally {
3429
+ // Reset the priority to the previous non-sync value.
3430
+ executionContext = prevExecutionContext;
3431
+ setCurrentUpdatePriority(previousPriority);
3432
+ ReactSharedInternals.T = prevTransition;
3433
+ }
3434
}
3435
3436
+ // Tell Scheduler to yield at the end of the frame, so the browser has an
3437
+ // opportunity to paint.
3438
+ requestPaint();
3439
+
3440
if (enableProfilerTimer && enableComponentPerformanceTrack) {
3441
recordCommitEndTime();
3442
logCommitPhase(
@@ -3439,18 +3448,22 @@ function commitRootImpl(
3448
);
3449
}
3450
3442
- const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
3451
+ const rootDidHavePassiveEffects = // If this subtree rendered with profiling this commit, we need to visit it to log it.
3452
+ (enableProfilerTimer &&
3453
+ enableComponentPerformanceTrack &&
3454
+ finishedWork.actualDuration !== 0) ||
3455
+ (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
3456
+ (finishedWork.flags & PassiveMask) !== NoFlags;
3457
3444
- if (rootDoesHavePassiveEffects) {
3458
+ if (rootDidHavePassiveEffects) {
3459
// This commit has passive effects. Stash a reference to them. But don't
3460
// schedule a callback until after flushing layout work.
3447
- rootDoesHavePassiveEffects = false;
3461
rootWithPendingPassiveEffects = root;
3462
pendingPassiveEffectsLanes = lanes;
3463
} else {
3464
// There were no passive effects, so we can immediately release the cache
3465
// pool for this render.
3453
- releaseRootPooledCache(root, remainingLanes);
3466
+ releaseRootPooledCache(root, root.pendingLanes);
3467
if (__DEV__) {
3468
nestedPassiveUpdateCount = 0;
3469
rootWithPassiveNestedUpdates = null;
@@ -3458,7 +3471,7 @@ function commitRootImpl(
3471
}
3472
3473
// Read this again, since an effect might have updated it
3461
- remainingLanes = root.pendingLanes;
3474
+ let remainingLanes = root.pendingLanes;
3475
3476
// Check if there's remaining work on this root
3477
// TODO: This is part of the `componentDidCatch` implementation. Its purpose
@@ -3482,7 +3495,8 @@ function commitRootImpl(
3495
}
3496
}
3497
3485
- onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);
3498
+ const renderPriority = lanesToEventPriority(lanes);
3499
+ onCommitRootDevTools(finishedWork.stateNode, renderPriority);
3500
3501
if (enableUpdaterTracking) {
3502
if (isDevToolsPresent) {
@@ -3495,22 +3509,31 @@ function commitRootImpl(
3509
}
3510
3511
if (recoverableErrors !== null) {
3498
- // There were errors during this render, but recovered from them without
3499
- // needing to surface it to the UI. We log them here.
3500
- const onRecoverableError = root.onRecoverableError;
3501
- for (let i = 0; i < recoverableErrors.length; i++) {
3502
- const recoverableError = recoverableErrors[i];
3503
- const errorInfo = makeErrorInfo(recoverableError.stack);
3504
- if (__DEV__) {
3505
- runWithFiberInDEV(
3506
- recoverableError.source,
3507
- onRecoverableError,
3508
- recoverableError.value,
3509
- errorInfo,
3510
- );
3511
- } else {
3512
- onRecoverableError(recoverableError.value, errorInfo);
3512
+ const prevTransition = ReactSharedInternals.T;
3513
+ const previousUpdateLanePriority = getCurrentUpdatePriority();
3514
+ setCurrentUpdatePriority(DiscreteEventPriority);
3515
+ ReactSharedInternals.T = null;
3516
+ try {
3517
+ // There were errors during this render, but recovered from them without
3518
+ // needing to surface it to the UI. We log them here.
3519
+ const onRecoverableError = root.onRecoverableError;
3520
+ for (let i = 0; i < recoverableErrors.length; i++) {
3521
+ const recoverableError = recoverableErrors[i];
3522
+ const errorInfo = makeErrorInfo(recoverableError.stack);
3523
+ if (__DEV__) {
3524
+ runWithFiberInDEV(
3525
+ recoverableError.source,
3526
+ onRecoverableError,
3527
+ recoverableError.value,
3528
+ errorInfo,
3529
+ );
3530
+ } else {
3531
+ onRecoverableError(recoverableError.value, errorInfo);
3532
+ }
3533
}
3534
+ } finally {
3535
+ ReactSharedInternals.T = prevTransition;
3536
+ setCurrentUpdatePriority(previousUpdateLanePriority);
3537
}
3538
}
3539
@@ -3610,8 +3633,6 @@ function commitRootImpl(
3633
});
3634
}
3635
}
3613
-
3614
- return null;
3636
}
3637
3638
function makeErrorInfo(componentStack: ?string) {
@@ -3705,7 +3726,6 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
3726
// We've finished our work for this render pass.
3727
root.callbackNode = null;
3728
root.callbackPriority = NoLane;
3708
- root.cancelPendingCommit = null;
3729
}
3730
3731
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
packages/react-reconciler/src/ReactInternalTypes.js
-4
@@ -220,8 +220,6 @@ type BaseFiberRootProperties = {
220
221
pingCache: WeakMap<Wakeable, Set<mixed>> | Map<Wakeable, Set<mixed>> | null,
222
223
- // A finished work-in-progress HostRoot that's ready to be committed.
224
- finishedWork: Fiber | null,
223
// Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
224
// it's superseded by a new one.
225
timeoutHandle: TimeoutHandle | NoTimeout,
@@ -252,8 +250,6 @@ type BaseFiberRootProperties = {
250
errorRecoveryDisabledLanes: Lanes,
251
shellSuspendCounter: number,
252
255
- finishedLanes: Lanes,
256
-
253
entangledLanes: Lanes,
254
entanglements: LaneMap<Lanes>,
255
packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js
+1
@@ -744,6 +744,7 @@ describe('ReactDeferredValue', () => {
744
</Container>,
745
);
746
// We should switch to pre-rendering the new preview.
747
+ await waitForPaint([]);
748
await waitForPaint(['Preview [B]']);
749
expect(root).toMatchRenderedOutput(<div hidden={true}>Preview [B]</div>);
750