@samitouri / QOS-React-1 / commits / b480865db0

[Fiber] Always flush Default priority in the microtask if a Transition was scheduled (#33186)

Stacked on #33160. The purpose of this is to avoid calling `onDefaultTransitionIndicator` when a Default priority update acts as the loading indicator, but still call it when unrelated Default updates happens nearby. When we schedule Default priority work that gets batched with other events in the same frame more or less. This helps optimize by doing less work. However, that batching means that we can't separate work from one setState from another. If we would consider all Default priority work in a frame when determining whether to show the default we might never show it in cases like when you have a recurring timer updating something. This instead flushes the Default priority work eagerly along with the sync work at the end of the event, if this event scheduled any Transition work. This is then used to determine if the default indicator needs to be shown.

Sebastian Markbåge committed May 13, 2025 at 15:52 UTC b480865db0babfcad602a1a1909775069b5779f9
2 files changed +9 -4
packages/react-reconciler/src/ReactFiberRootScheduler.js
+8
@@ -26,6 +26,7 @@ import {
26 NoLane,
27 NoLanes,
28 SyncLane,
29 + DefaultLane,
30 getHighestPriorityLane,
31 getNextLanes,
32 includesSyncLane,
@@ -261,6 +262,13 @@ function processRootScheduleInMicrotask() {
262 // render it synchronously anyway. We do this during a popstate event to
263 // preserve the scroll position of the previous page.
264 syncTransitionLanes = currentEventTransitionLane;
265 + } else if (enableDefaultTransitionIndicator) {
266 + // If we have a Transition scheduled by this event it might be paired
267 + // with Default lane scheduled loading indicators. To unbatch it from
268 + // other events later on, flush it early to determine whether it
269 + // rendered an indicator. This ensures that setState in default priority
270 + // event doesn't trigger onDefaultTransitionIndicator.
271 + syncTransitionLanes = DefaultLane;
272 }
273 }
274
packages/react-reconciler/src/__tests__/ReactDefaultTransitionIndicator-test.js
+1 -4
@@ -109,10 +109,7 @@ describe('ReactDefaultTransitionIndicator', () => {
109 expect(root).toMatchRenderedOutput(<div>Hi</div>);
110
111 await act(() => {
112 - // TODO: This should not require a discrete update ideally but work for default too.
113 - ReactNoop.discreteUpdates(() => {
114 - update('Loading...');
115 - });
112 + update('Loading...');
113 React.startTransition(() => {
114 update('');
115 root.render(<App>{promiseB}</App>);