diff --git a/packages/react-reconciler/src/ReactFiberLane.new.js b/packages/react-reconciler/src/ReactFiberLane.new.js
index ced32541ee8df..359afba7095e8 100644
--- a/packages/react-reconciler/src/ReactFiberLane.new.js
+++ b/packages/react-reconciler/src/ReactFiberLane.new.js
@@ -39,6 +39,7 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
+ enableSyncDefaultUpdates,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
@@ -273,6 +274,18 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}
}
+ if (
+ // TODO: Check for root override, once that lands
+ enableSyncDefaultUpdates &&
+ (nextLanes & InputContinuousLane) !== NoLanes
+ ) {
+ // When updates are sync by default, we entangle continous priority updates
+ // and default updates, so they render in the same batch. The only reason
+ // they use separate lanes is because continuous updates should interrupt
+ // transitions, but default updates should not.
+ nextLanes |= pendingLanes & DefaultLane;
+ }
+
// Check for entangled lanes and add them to the batch.
//
// A lane is said to be entangled with another when it's not allowed to render
diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js
index 5af142edbbae9..5db285a3fa24d 100644
--- a/packages/react-reconciler/src/ReactFiberLane.old.js
+++ b/packages/react-reconciler/src/ReactFiberLane.old.js
@@ -39,6 +39,7 @@ import {
enableCache,
enableSchedulingProfiler,
enableUpdaterTracking,
+ enableSyncDefaultUpdates,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
@@ -273,6 +274,18 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
}
}
+ if (
+ // TODO: Check for root override, once that lands
+ enableSyncDefaultUpdates &&
+ (nextLanes & InputContinuousLane) !== NoLanes
+ ) {
+ // When updates are sync by default, we entangle continous priority updates
+ // and default updates, so they render in the same batch. The only reason
+ // they use separate lanes is because continuous updates should interrupt
+ // transitions, but default updates should not.
+ nextLanes |= pendingLanes & DefaultLane;
+ }
+
// Check for entangled lanes and add them to the batch.
//
// A lane is said to be entangled with another when it's not allowed to render
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index 665df80b13347..934f2697380ae 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -139,8 +139,8 @@ import {
NoLanes,
NoLane,
SyncLane,
- DefaultLane,
DefaultHydrationLane,
+ DefaultLane,
InputContinuousLane,
InputContinuousHydrationLane,
NoTimestamp,
@@ -437,13 +437,6 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
- if (
- enableSyncDefaultUpdates &&
- (updateLane === InputContinuousLane ||
- updateLane === InputContinuousHydrationLane)
- ) {
- return DefaultLane;
- }
return updateLane;
}
@@ -454,13 +447,6 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
- if (
- enableSyncDefaultUpdates &&
- (eventLane === InputContinuousLane ||
- eventLane === InputContinuousHydrationLane)
- ) {
- return DefaultLane;
- }
return eventLane;
}
@@ -814,7 +800,9 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
let exitStatus =
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
- includesSomeLane(lanes, DefaultHydrationLane))
+ includesSomeLane(lanes, InputContinuousLane) ||
+ includesSomeLane(lanes, DefaultHydrationLane) ||
+ includesSomeLane(lanes, InputContinuousHydrationLane))
? // Time slicing is disabled for default updates in this root.
renderRootSync(root, lanes)
: renderRootConcurrent(root, lanes);
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index baebee3dc0490..97416c4b05641 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -139,8 +139,8 @@ import {
NoLanes,
NoLane,
SyncLane,
- DefaultLane,
DefaultHydrationLane,
+ DefaultLane,
InputContinuousLane,
InputContinuousHydrationLane,
NoTimestamp,
@@ -437,13 +437,6 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
- if (
- enableSyncDefaultUpdates &&
- (updateLane === InputContinuousLane ||
- updateLane === InputContinuousHydrationLane)
- ) {
- return DefaultLane;
- }
return updateLane;
}
@@ -454,13 +447,6 @@ export function requestUpdateLane(fiber: Fiber): Lane {
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
- if (
- enableSyncDefaultUpdates &&
- (eventLane === InputContinuousLane ||
- eventLane === InputContinuousHydrationLane)
- ) {
- return DefaultLane;
- }
return eventLane;
}
@@ -814,7 +800,9 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
let exitStatus =
enableSyncDefaultUpdates &&
(includesSomeLane(lanes, DefaultLane) ||
- includesSomeLane(lanes, DefaultHydrationLane))
+ includesSomeLane(lanes, InputContinuousLane) ||
+ includesSomeLane(lanes, DefaultHydrationLane) ||
+ includesSomeLane(lanes, InputContinuousHydrationLane))
? // Time slicing is disabled for default updates in this root.
renderRootSync(root, lanes)
: renderRootConcurrent(root, lanes);
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index 485d477ed7e8e..07d3f7ff526aa 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -1405,18 +1405,6 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.unstable_runWithPriority(ContinuousEventPriority, () => {
setParentState(false);
});
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- // TODO: Default updates do not interrupt transition updates, to
- // prevent starvation. However, when sync default updates are enabled,
- // continuous updates are treated like default updates. In this case,
- // we probably don't want this behavior; continuous should be allowed
- // to interrupt.
- expect(Scheduler).toFlushUntilNextPaint([
- 'Child two render',
- 'Child one commit',
- 'Child two commit',
- ]);
- }
expect(Scheduler).toFlushUntilNextPaint([
'Parent false render',
'Parent false commit',
diff --git a/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js b/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js
index 0aee7df66df4a..116cc4c4d8e00 100644
--- a/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js
@@ -1,6 +1,8 @@
let React;
let ReactNoop;
let Scheduler;
+let ContinuousEventPriority;
+let startTransition;
let useState;
let useEffect;
@@ -11,6 +13,9 @@ describe('ReactUpdatePriority', () => {
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
+ ContinuousEventPriority = require('react-reconciler/constants')
+ .ContinuousEventPriority;
+ startTransition = React.unstable_startTransition;
useState = React.useState;
useEffect = React.useEffect;
});
@@ -78,4 +83,53 @@ describe('ReactUpdatePriority', () => {
// Now the idle update has flushed
expect(Scheduler).toHaveYielded(['Idle: 2, Default: 2']);
});
+
+ // @gate experimental
+ test('continuous updates should interrupt transisions', async () => {
+ const root = ReactNoop.createRoot();
+
+ let setCounter;
+ let setIsHidden;
+ function App() {
+ const [counter, _setCounter] = useState(1);
+ const [isHidden, _setIsHidden] = useState(false);
+ setCounter = _setCounter;
+ setIsHidden = _setIsHidden;
+ if (isHidden) {
+ return ;
+ }
+ return (
+ <>
+
+
+
+ >
+ );
+ }
+
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['A1', 'B1', 'C1']);
+ expect(root).toMatchRenderedOutput('A1B1C1');
+
+ await ReactNoop.act(async () => {
+ startTransition(() => {
+ setCounter(2);
+ });
+ expect(Scheduler).toFlushAndYieldThrough(['A2']);
+ ReactNoop.unstable_runWithPriority(ContinuousEventPriority, () => {
+ setIsHidden(true);
+ });
+ });
+ expect(Scheduler).toHaveYielded([
+ // Because the hide update has continous priority, it should interrupt the
+ // in-progress transition
+ '(hidden)',
+ // When the transition resumes, it's a no-op because the children are
+ // now hidden.
+ '(hidden)',
+ ]);
+ expect(root).toMatchRenderedOutput('(hidden)');
+ });
});
diff --git a/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js b/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js
index da59c153f43dd..6474be855b38f 100644
--- a/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js
@@ -168,18 +168,10 @@ describe('SchedulingProfiler labels', () => {
event.initEvent('mouseover', true, true);
dispatchAndSetCurrentEvent(targetRef.current, event);
});
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- expect(clearedMarks).toContain(
- `--schedule-state-update-${formatLanes(
- ReactFiberLane.DefaultLane,
- )}-App`,
- );
- } else {
- expect(clearedMarks).toContain(
- `--schedule-state-update-${formatLanes(
- ReactFiberLane.InputContinuousLane,
- )}-App`,
- );
- }
+ expect(clearedMarks).toContain(
+ `--schedule-state-update-${formatLanes(
+ ReactFiberLane.InputContinuousLane,
+ )}-App`,
+ );
});
});