diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index cde23ef9a6249..e7d842bdbdc07 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -625,6 +625,8 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) { // idle updates until after all the regular updates have finished; there's no // way it could unblock a transition. if (updateLane !== IdleLane) { + movePendingUpdatersToLane(root, root.pingedLanes, updateLane); + root.suspendedLanes = NoLanes; root.pingedLanes = NoLanes; } @@ -656,7 +658,8 @@ export function markRootSuspended( } export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) { - root.pingedLanes |= root.suspendedLanes & pingedLanes; + // TODO: When would we ever ping lanes that we aren't suspended on? + root.pingedLanes |= pingedLanes; } export function markRootFinished( @@ -905,6 +908,35 @@ export function addFiberToLanesMap( } } +function movePendingUpdatersToLane( + root: FiberRoot, + sourceLanes: Lanes, + targetLane: Lane, +) { + if (!enableUpdaterTracking) { + return; + } + if (!isDevToolsPresent) { + return; + } + const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap; + const targetIndex = laneToIndex(targetLane); + const targetUpdaters = pendingUpdatersLaneMap[targetIndex]; + let lanes = sourceLanes; + while (lanes > 0) { + const index = laneToIndex(lanes); + const lane = 1 << index; + + const sourceUpdaters = pendingUpdatersLaneMap[index]; + sourceUpdaters.forEach(sourceUpdater => { + targetUpdaters.add(sourceUpdater); + }); + sourceUpdaters.clear(); + + lanes &= ~lane; + } +} + export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) { if (!enableUpdaterTracking) { return; diff --git a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js index 3f7fcd28aec3d..e7c091775d394 100644 --- a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js @@ -49,6 +49,15 @@ describe('updaters', () => { schedulerTags.push(fiber.tag); schedulerTypes.push(fiber.elementType); }); + fiberRoot.pendingUpdatersLaneMap.forEach((pendingUpdaters, index) => { + if (pendingUpdaters.size > 0) { + // TODO: Is it ever ok to have dangling pending updaters or is this always a bug? + // const lane = 1 << index; + // throw new Error( + // `Lane ${lane} has pending updaters. Either you didn't assert on all updates in your test or React is leaking updaters.`, + // ); + } + }); allSchedulerTags.push(schedulerTags); allSchedulerTypes.push(schedulerTypes); }), @@ -266,9 +275,6 @@ describe('updaters', () => { await waitForAll([]); }); - // This test should be convertable to createRoot but the allScheduledTypes assertions are no longer the same - // So I'm leaving it in legacy mode for now and just disabling if legacy mode is turned off - // @gate !disableLegacyMode it('should cover suspense pings', async () => { let data = null; let resolver = null; @@ -303,10 +309,11 @@ describe('updaters', () => { } }; + const root = ReactDOMClient.createRoot(document.createElement('div')); await act(() => { - ReactDOM.render(, document.createElement('div')); - assertLog(['onCommitRoot']); + root.render(); }); + assertLog(['onCommitRoot']); expect(setShouldSuspend).not.toBeNull(); expect(allSchedulerTypes).toEqual([[null]]);