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]]);