@samitouri / QOS-React / commits / c81312e3a7

[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