diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
index a94cbe367a8ae..3360ef2cbd52a 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
@@ -275,17 +275,32 @@ describe('ReactDOMFiberAsync', () => {
expect(ops).toEqual([]);
});
// Only the active updates have flushed
- expect(container.textContent).toEqual('BC');
- expect(ops).toEqual(['BC']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(container.textContent).toEqual('ABC');
+ expect(ops).toEqual(['ABC']);
+ } else {
+ expect(container.textContent).toEqual('BC');
+ expect(ops).toEqual(['BC']);
+ }
- instance.push('D');
- expect(container.textContent).toEqual('BC');
- expect(ops).toEqual(['BC']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ instance.push('D');
+ expect(container.textContent).toEqual('ABC');
+ expect(ops).toEqual(['ABC']);
+ } else {
+ instance.push('D');
+ expect(container.textContent).toEqual('BC');
+ expect(ops).toEqual(['BC']);
+ }
// Flush the async updates
Scheduler.unstable_flushAll();
expect(container.textContent).toEqual('ABCD');
- expect(ops).toEqual(['BC', 'ABCD']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(ops).toEqual(['ABC', 'ABCD']);
+ } else {
+ expect(ops).toEqual(['BC', 'ABCD']);
+ }
});
// @gate www
diff --git a/packages/react-reconciler/src/ReactFiberLane.new.js b/packages/react-reconciler/src/ReactFiberLane.new.js
index cc3781bd7b3ed..de2e119cc3c63 100644
--- a/packages/react-reconciler/src/ReactFiberLane.new.js
+++ b/packages/react-reconciler/src/ReactFiberLane.new.js
@@ -23,6 +23,7 @@ import {
enableUpdaterTracking,
allowConcurrentByDefault,
enableTransitionTracing,
+ enableSyncDefaultUpdates,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
@@ -135,8 +136,28 @@ let nextRetryLane: Lane = RetryLane1;
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
switch (getHighestPriorityLane(lanes)) {
case SyncHydrationLane:
+ if (enableSyncDefaultUpdates) {
+ let ret = SyncHydrationLane;
+ if (lanes & DefaultHydrationLane) {
+ ret |= DefaultHydrationLane;
+ }
+ if (lanes & InputContinuousHydrationLane) {
+ ret |= InputContinuousHydrationLane;
+ }
+ return ret;
+ }
return SyncHydrationLane;
case SyncLane:
+ if (enableSyncDefaultUpdates) {
+ let ret = SyncLane;
+ if (lanes & DefaultLane) {
+ ret |= DefaultLane;
+ }
+ if (lanes & InputContinuousLane) {
+ ret |= InputContinuousLane;
+ }
+ return ret;
+ }
return SyncLane;
case InputContinuousHydrationLane:
return InputContinuousHydrationLane;
@@ -251,7 +272,10 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// Default priority updates should not interrupt transition updates. The
// only difference between default updates and transition updates is that
// default updates do not support refresh transitions.
- (nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
+ // Interrupt transtion if default is batched with sync.
+ (!enableSyncDefaultUpdates &&
+ nextLane === DefaultLane &&
+ (wipLane & TransitionLanes) !== NoLanes)
) {
// Keep working on the existing in-progress tree. Do not interrupt.
return wipLanes;
diff --git a/packages/react-reconciler/src/ReactFiberLane.old.js b/packages/react-reconciler/src/ReactFiberLane.old.js
index 110893a036530..3c9ff604a558f 100644
--- a/packages/react-reconciler/src/ReactFiberLane.old.js
+++ b/packages/react-reconciler/src/ReactFiberLane.old.js
@@ -23,6 +23,7 @@ import {
enableUpdaterTracking,
allowConcurrentByDefault,
enableTransitionTracing,
+ enableSyncDefaultUpdates,
} from 'shared/ReactFeatureFlags';
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
@@ -135,8 +136,28 @@ let nextRetryLane: Lane = RetryLane1;
function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {
switch (getHighestPriorityLane(lanes)) {
case SyncHydrationLane:
+ if (enableSyncDefaultUpdates) {
+ let ret = SyncHydrationLane;
+ if (lanes & DefaultHydrationLane) {
+ ret |= DefaultHydrationLane;
+ }
+ if (lanes & InputContinuousHydrationLane) {
+ ret |= InputContinuousHydrationLane;
+ }
+ return ret;
+ }
return SyncHydrationLane;
case SyncLane:
+ if (enableSyncDefaultUpdates) {
+ let ret = SyncLane;
+ if (lanes & DefaultLane) {
+ ret |= DefaultLane;
+ }
+ if (lanes & InputContinuousLane) {
+ ret |= InputContinuousLane;
+ }
+ return ret;
+ }
return SyncLane;
case InputContinuousHydrationLane:
return InputContinuousHydrationLane;
@@ -251,7 +272,10 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
// Default priority updates should not interrupt transition updates. The
// only difference between default updates and transition updates is that
// default updates do not support refresh transitions.
- (nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)
+ // Interrupt transtion if default is batched with sync.
+ (!enableSyncDefaultUpdates &&
+ nextLane === DefaultLane &&
+ (wipLane & TransitionLanes) !== NoLanes)
) {
// Keep working on the existing in-progress tree. Do not interrupt.
return wipLanes;
diff --git a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js
index 7998d2a23177e..6b4771493a194 100644
--- a/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js
@@ -157,12 +157,18 @@ describe('ReactBlockingMode', () => {
}),
);
- // Only the second update should have flushed synchronously
- expect(Scheduler).toHaveYielded(['B1']);
- expect(root).toMatchRenderedOutput('A0B1');
-
// Now flush the first update
- expect(Scheduler).toFlushAndYield(['A1']);
- expect(root).toMatchRenderedOutput('A1B1');
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded(['A1', 'B1']);
+ expect(root).toMatchRenderedOutput('A1B1');
+ } else {
+ // Only the second update should have flushed synchronously
+ expect(Scheduler).toHaveYielded(['B1']);
+ expect(root).toMatchRenderedOutput('A0B1');
+
+ // Now flush the first update
+ expect(Scheduler).toFlushAndYield(['A1']);
+ expect(root).toMatchRenderedOutput('A1B1');
+ }
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js b/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js
index 0f97bb5997186..372151466f427 100644
--- a/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactClassSetStateCallback-test.js
@@ -35,9 +35,17 @@ describe('ReactClassSetStateCallback', () => {
expect(Scheduler).toHaveYielded([0]);
await act(async () => {
- app.setState({step: 1}, () =>
- Scheduler.unstable_yieldValue('Callback 1'),
- );
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ React.startTransition(() => {
+ app.setState({step: 1}, () =>
+ Scheduler.unstable_yieldValue('Callback 1'),
+ );
+ });
+ } else {
+ app.setState({step: 1}, () =>
+ Scheduler.unstable_yieldValue('Callback 1'),
+ );
+ }
ReactNoop.flushSync(() => {
app.setState({step: 2}, () =>
Scheduler.unstable_yieldValue('Callback 2'),
diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
index 24127589dfd90..72ca257fefa8d 100644
--- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
@@ -339,7 +339,9 @@ describe('ReactExpiration', () => {
// Before the update can finish, update again. Even though no time has
// advanced, this update should be given a different expiration time than
// the currently rendering one. So, C and D should render with 1, not 2.
- subscribers.forEach(s => s.setState({text: '2'}));
+ React.startTransition(() => {
+ subscribers.forEach(s => s.setState({text: '2'}));
+ });
expect(Scheduler).toFlushAndYieldThrough([
'1 [C] [render]',
'1 [D] [render]',
diff --git a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
index 07cc3437a5e24..1c1d79e841751 100644
--- a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
@@ -54,15 +54,21 @@ describe('ReactFlushSync', () => {
// The passive effect will schedule a sync update and a normal update.
// They should commit in two separate batches. First the sync one.
expect(() => {
- expect(Scheduler).toFlushUntilNextPaint(['1, 0']);
+ expect(Scheduler).toFlushUntilNextPaint(
+ gate(flags => flags.enableSyncDefaultUpdates) ? ['1, 1'] : ['1', '0'],
+ );
}).toErrorDev('flushSync was called from inside a lifecycle method');
// The remaining update is not sync
ReactNoop.flushSync();
expect(Scheduler).toHaveYielded([]);
- // Now flush it.
- expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushUntilNextPaint([]);
+ } else {
+ // Now flush it.
+ expect(Scheduler).toFlushUntilNextPaint(['1 / 1']);
+ }
});
expect(root).toMatchRenderedOutput('1, 1');
});
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index 37fd06f1e0260..618c7cdff082b 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -568,9 +568,13 @@ describe('ReactHooks', () => {
});
};
- // Update at normal priority
- ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
-
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ // Update at transition priority
+ React.startTransition(() => update(n => n * 100));
+ } else {
+ // Update at normal priority
+ ReactTestRenderer.unstable_batchedUpdates(() => update(n => n * 100));
+ }
// The new state is eagerly computed.
expect(Scheduler).toHaveYielded(['Compute state (1 -> 100)']);
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index d16b07fc0772f..6cdde2b901632 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -815,7 +815,13 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.discreteUpdates(() => {
setRow(5);
});
- setRow(20);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ React.startTransition(() => {
+ setRow(20);
+ });
+ } else {
+ setRow(20);
+ }
});
expect(Scheduler).toHaveYielded(['Up', 'Down']);
expect(root).toMatchRenderedOutput();
@@ -955,11 +961,15 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.flushSync(() => {
counter.current.dispatch(INCREMENT);
});
- expect(Scheduler).toHaveYielded(['Count: 1']);
- expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
-
- expect(Scheduler).toFlushAndYield(['Count: 4']);
- expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded(['Count: 4']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
+ } else {
+ expect(Scheduler).toHaveYielded(['Count: 1']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
+ expect(Scheduler).toFlushAndYield(['Count: 4']);
+ expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]);
+ }
});
});
@@ -1717,11 +1727,15 @@ describe('ReactHooksWithNoopRenderer', () => {
// As a result we, somewhat surprisingly, commit them in the opposite order.
// This should be fine because any non-discrete set of work doesn't guarantee order
// and easily could've happened slightly later too.
- expect(Scheduler).toHaveYielded([
- 'Will set count to 1',
- 'Count: 2',
- 'Count: 1',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded(['Will set count to 1', 'Count: 1']);
+ } else {
+ expect(Scheduler).toHaveYielded([
+ 'Will set count to 1',
+ 'Count: 2',
+ 'Count: 1',
+ ]);
+ }
expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
index 8b6226888b2aa..3b047a2973ed7 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
@@ -1910,21 +1910,37 @@ describe('ReactIncremental', () => {
,
);
- expect(Scheduler).toFlushAndYield([
- 'ShowLocale {"locale":"sv"}',
- 'ShowBoth {"locale":"sv"}',
- 'Intl {}',
- 'ShowLocale {"locale":"en"}',
- 'Router {}',
- 'Indirection {}',
- 'ShowLocale {"locale":"en"}',
- 'ShowRoute {"route":"/about"}',
- 'ShowNeither {}',
- 'Intl {}',
- 'ShowBoth {"locale":"ru","route":"/about"}',
- 'ShowBoth {"locale":"en","route":"/about"}',
- 'ShowBoth {"locale":"en"}',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushAndYield([
+ 'Intl {}',
+ 'ShowLocale {"locale":"en"}',
+ 'Router {}',
+ 'Indirection {}',
+ 'ShowLocale {"locale":"en"}',
+ 'ShowRoute {"route":"/about"}',
+ 'ShowNeither {}',
+ 'Intl {}',
+ 'ShowBoth {"locale":"ru","route":"/about"}',
+ 'ShowBoth {"locale":"en","route":"/about"}',
+ 'ShowBoth {"locale":"en"}',
+ ]);
+ } else {
+ expect(Scheduler).toFlushAndYield([
+ 'ShowLocale {"locale":"sv"}',
+ 'ShowBoth {"locale":"sv"}',
+ 'Intl {}',
+ 'ShowLocale {"locale":"en"}',
+ 'Router {}',
+ 'Indirection {}',
+ 'ShowLocale {"locale":"en"}',
+ 'ShowRoute {"route":"/about"}',
+ 'ShowNeither {}',
+ 'Intl {}',
+ 'ShowBoth {"locale":"ru","route":"/about"}',
+ 'ShowBoth {"locale":"en","route":"/about"}',
+ 'ShowBoth {"locale":"en"}',
+ ]);
+ }
});
it('does not leak own context into context provider', () => {
@@ -2758,7 +2774,11 @@ describe('ReactIncremental', () => {
// Interrupt at same priority
ReactNoop.render();
- expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushAndYield(['Parent: 2', 'Child: 2']);
+ } else {
+ expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
+ }
});
it('does not interrupt for update at lower priority', () => {
@@ -2785,7 +2805,11 @@ describe('ReactIncremental', () => {
ReactNoop.expire(2000);
ReactNoop.render();
- expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushAndYield(['Parent: 2', 'Child: 2']);
+ } else {
+ expect(Scheduler).toFlushAndYield(['Child: 1', 'Parent: 2', 'Child: 2']);
+ }
});
it('does interrupt for update at higher priority', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
index 454a0399b9a52..552ff16148229 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
@@ -47,7 +47,7 @@ describe('ReactIncrementalUpdates', () => {
state = {};
componentDidMount() {
Scheduler.unstable_yieldValue('commit');
- ReactNoop.deferredUpdates(() => {
+ React.startTransition(() => {
// Has low priority
this.setState({b: 'b'});
this.setState({c: 'c'});
@@ -111,13 +111,13 @@ describe('ReactIncrementalUpdates', () => {
expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']);
ReactNoop.flushSync(() => {
- ReactNoop.deferredUpdates(() => {
+ React.startTransition(() => {
instance.setState({x: 'x'});
instance.setState({y: 'y'});
});
instance.setState({a: 'a'});
instance.setState({b: 'b'});
- ReactNoop.deferredUpdates(() => {
+ React.startTransition(() => {
instance.updater.enqueueReplaceState(instance, {c: 'c'});
instance.setState({d: 'd'});
});
@@ -190,27 +190,40 @@ describe('ReactIncrementalUpdates', () => {
});
// The sync updates should have flushed, but not the async ones
- expect(Scheduler).toHaveYielded(['e', 'f']);
- expect(ReactNoop.getChildren()).toEqual([span('ef')]);
+ expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
+ expect(ReactNoop.getChildren()).toEqual([span('def')]);
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
- expect(Scheduler).toFlushAndYield([
- // Since 'g' is in a transition, we'll process 'd' separately first.
- // That causes us to process 'd' with 'e' and 'f' rebased.
- 'd',
- 'e',
- 'f',
- // Then we'll re-process everything for 'g'.
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushAndYield([
+ // Then we'll re-process everything for 'g'.
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ]);
+ } else {
+ expect(Scheduler).toFlushAndYield([
+ // Since 'g' is in a transition, we'll process 'd' separately first.
+ // That causes us to process 'd' with 'e' and 'f' rebased.
+ 'd',
+ 'e',
+ 'f',
+ // Then we'll re-process everything for 'g'.
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ]);
+ }
expect(ReactNoop.getChildren()).toEqual([span('abcdefg')]);
} else {
instance.setState(createUpdate('d'));
@@ -292,27 +305,40 @@ describe('ReactIncrementalUpdates', () => {
});
// The sync updates should have flushed, but not the async ones.
- expect(Scheduler).toHaveYielded(['e', 'f']);
+ expect(Scheduler).toHaveYielded(['d', 'e', 'f']);
expect(ReactNoop.getChildren()).toEqual([span('f')]);
// Now flush the remaining work. Even though e and f were already processed,
// they should be processed again, to ensure that the terminal state
// is deterministic.
- expect(Scheduler).toFlushAndYield([
- // Since 'g' is in a transition, we'll process 'd' separately first.
- // That causes us to process 'd' with 'e' and 'f' rebased.
- 'd',
- 'e',
- 'f',
- // Then we'll re-process everything for 'g'.
- 'a',
- 'b',
- 'c',
- 'd',
- 'e',
- 'f',
- 'g',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toFlushAndYield([
+ // Then we'll re-process everything for 'g'.
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ]);
+ } else {
+ expect(Scheduler).toFlushAndYield([
+ // Since 'g' is in a transition, we'll process 'd' separately first.
+ // That causes us to process 'd' with 'e' and 'f' rebased.
+ 'd',
+ 'e',
+ 'f',
+ // Then we'll re-process everything for 'g'.
+ 'a',
+ 'b',
+ 'c',
+ 'd',
+ 'e',
+ 'f',
+ 'g',
+ ]);
+ }
expect(ReactNoop.getChildren()).toEqual([span('fg')]);
} else {
instance.setState(createUpdate('d'));
@@ -688,21 +714,29 @@ describe('ReactIncrementalUpdates', () => {
pushToLog('B'),
);
});
- expect(Scheduler).toHaveYielded([
- // A and B are pending. B is higher priority, so we'll render that first.
- 'Committed: B',
- // Because A comes first in the queue, we're now in rebase mode. B must
- // be rebased on top of A. Also, in a layout effect, we received two new
- // updates: C and D. C is user-blocking and D is synchronous.
- //
- // First render the synchronous update. What we're testing here is that
- // B *is not dropped* even though it has lower than sync priority. That's
- // because we already committed it. However, this render should not
- // include C, because that update wasn't already committed.
- 'Committed: BD',
- 'Committed: BCD',
- 'Committed: ABCD',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded([
+ 'Committed: B',
+ 'Committed: BCD',
+ 'Committed: ABCD',
+ ]);
+ } else {
+ expect(Scheduler).toHaveYielded([
+ // A and B are pending. B is higher priority, so we'll render that first.
+ 'Committed: B',
+ // Because A comes first in the queue, we're now in rebase mode. B must
+ // be rebased on top of A. Also, in a layout effect, we received two new
+ // updates: C and D. C is user-blocking and D is synchronous.
+ //
+ // First render the synchronous update. What we're testing here is that
+ // B *is not dropped* even though it has lower than sync priority. That's
+ // because we already committed it. However, this render should not
+ // include C, because that update wasn't already committed.
+ 'Committed: BD',
+ 'Committed: BCD',
+ 'Committed: ABCD',
+ ]);
+ }
expect(root).toMatchRenderedOutput('ABCD');
});
@@ -748,21 +782,29 @@ describe('ReactIncrementalUpdates', () => {
pushToLog('B'),
);
});
- expect(Scheduler).toHaveYielded([
- // A and B are pending. B is higher priority, so we'll render that first.
- 'Committed: B',
- // Because A comes first in the queue, we're now in rebase mode. B must
- // be rebased on top of A. Also, in a layout effect, we received two new
- // updates: C and D. C is user-blocking and D is synchronous.
- //
- // First render the synchronous update. What we're testing here is that
- // B *is not dropped* even though it has lower than sync priority. That's
- // because we already committed it. However, this render should not
- // include C, because that update wasn't already committed.
- 'Committed: BD',
- 'Committed: BCD',
- 'Committed: ABCD',
- ]);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded([
+ 'Committed: B',
+ 'Committed: BCD',
+ 'Committed: ABCD',
+ ]);
+ } else {
+ expect(Scheduler).toHaveYielded([
+ // A and B are pending. B is higher priority, so we'll render that first.
+ 'Committed: B',
+ // Because A comes first in the queue, we're now in rebase mode. B must
+ // be rebased on top of A. Also, in a layout effect, we received two new
+ // updates: C and D. C is user-blocking and D is synchronous.
+ //
+ // First render the synchronous update. What we're testing here is that
+ // B *is not dropped* even though it has lower than sync priority. That's
+ // because we already committed it. However, this render should not
+ // include C, because that update wasn't already committed.
+ 'Committed: BD',
+ 'Committed: BCD',
+ 'Committed: ABCD',
+ ]);
+ }
expect(root).toMatchRenderedOutput('ABCD');
});
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
index d830265610823..7f77ba535517c 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
@@ -673,8 +673,15 @@ describe('ReactOffscreen', () => {
);
// Before the inner update can finish, we receive another pair of updates.
- setOuter(2);
- setInner(2);
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ React.startTransition(() => {
+ setOuter(2);
+ setInner(2);
+ });
+ } else {
+ setOuter(2);
+ setInner(2);
+ }
// Also, before either of these new updates are processed, the hidden
// tree is revealed at high priority.
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
index e1a6ca49fd64c..272979fab6444 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
@@ -381,7 +381,9 @@ describe('ReactOffscreen', () => {
expect(root).toMatchRenderedOutput(A0);
await act(async () => {
- setStep(1);
+ React.startTransition(() => {
+ setStep(1);
+ });
ReactNoop.flushSync(() => {
setText('B');
});
@@ -513,8 +515,10 @@ describe('ReactOffscreen', () => {
// Before the tree commits, schedule a concurrent event. The inner update
// is to a tree that's just about to be hidden.
- setOuter(2);
- setInner(2);
+ startTransition(() => {
+ setOuter(2);
+ setInner(2);
+ });
// Commit the previous render.
jest.runAllTimers();
diff --git a/packages/react-reconciler/src/__tests__/ReactTransition-test.js b/packages/react-reconciler/src/__tests__/ReactTransition-test.js
index 560bb527b1b0e..df9a4b48579ff 100644
--- a/packages/react-reconciler/src/__tests__/ReactTransition-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactTransition-test.js
@@ -934,16 +934,30 @@ describe('ReactTransition', () => {
updateNormalPri();
});
- expect(Scheduler).toHaveYielded([
- // Finish transition update.
- 'Normal pri: 0',
- 'Commit',
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ expect(Scheduler).toHaveYielded([
+ // Interrupt transition.
+ 'Transition pri: 0',
+ 'Normal pri: 1',
+ 'Commit',
- // Normal pri update.
- 'Transition pri: 1',
- 'Normal pri: 1',
- 'Commit',
- ]);
+ // Normal pri update.
+ 'Transition pri: 1',
+ 'Normal pri: 1',
+ 'Commit',
+ ]);
+ } else {
+ expect(Scheduler).toHaveYielded([
+ // Finish transition update.
+ 'Normal pri: 0',
+ 'Commit',
+
+ // Normal pri update.
+ 'Transition pri: 1',
+ 'Normal pri: 1',
+ 'Commit',
+ ]);
+ }
expect(root).toMatchRenderedOutput('Transition pri: 1, Normal pri: 1');
});
diff --git a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
index 6321168d70aa3..81c1b7bc47e9e 100644
--- a/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
@@ -1558,8 +1558,15 @@ describe('useMutableSource', () => {
expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']);
// Mutate in an event. This schedules a subscription update on a, which
// already mounted, but not b, which hasn't subscribed yet.
- mutateA('a1');
- mutateB('b1');
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ React.startTransition(() => {
+ mutateA('a1');
+ mutateB('b1');
+ });
+ } else {
+ mutateA('a1');
+ mutateB('b1');
+ }
// Mutate again at lower priority. This will schedule another subscription
// update on a, but not b. When b mounts and subscriptions, the value it
diff --git a/packages/use-subscription/src/__tests__/useSubscription-test.js b/packages/use-subscription/src/__tests__/useSubscription-test.js
index 985312abd1d64..1d481e1d599b4 100644
--- a/packages/use-subscription/src/__tests__/useSubscription-test.js
+++ b/packages/use-subscription/src/__tests__/useSubscription-test.js
@@ -454,7 +454,13 @@ describe('useSubscription', () => {
observableA.next('a-2');
// Update again
- renderer.update();
+ if (gate(flags => flags.enableSyncDefaultUpdates)) {
+ React.startTransition(() => {
+ renderer.update();
+ });
+ } else {
+ renderer.update();
+ }
// Flush everything and ensure that the correct subscribable is used
expect(Scheduler).toFlushAndYield([