Skip to content

Commit 3128d43

Browse files
committed
Add unstable_concurrentUpdatesByDefault
1 parent a0d6b15 commit 3128d43

24 files changed

+159
-38
lines changed

packages/react-dom/src/__tests__/ReactDOMRoot-test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ let React = require('react');
1313
let ReactDOM = require('react-dom');
1414
let ReactDOMServer = require('react-dom/server');
1515
let Scheduler = require('scheduler');
16+
let act;
1617

1718
describe('ReactDOMRoot', () => {
1819
let container;
@@ -24,6 +25,7 @@ describe('ReactDOMRoot', () => {
2425
ReactDOM = require('react-dom');
2526
ReactDOMServer = require('react-dom/server');
2627
Scheduler = require('scheduler');
28+
act = require('react-dom/test-utils').unstable_concurrentAct;
2729
});
2830

2931
if (!__EXPERIMENTAL__) {
@@ -316,4 +318,37 @@ describe('ReactDOMRoot', () => {
316318
{withoutStack: true},
317319
);
318320
});
321+
322+
// @gate experimental
323+
it('opts-in to concurrent default updates', async () => {
324+
const root = ReactDOM.unstable_createRoot(container, {
325+
unstable_concurrentUpdatesByDefault: true,
326+
});
327+
328+
function Foo({value}) {
329+
Scheduler.unstable_yieldValue(value);
330+
return <div>{value}</div>;
331+
}
332+
333+
await act(async () => {
334+
root.render(<Foo value="a" />);
335+
});
336+
337+
expect(container.textContent).toEqual('a');
338+
339+
await act(async () => {
340+
root.render(<Foo value="b" />);
341+
342+
expect(Scheduler).toHaveYielded(['a']);
343+
expect(container.textContent).toEqual('a');
344+
345+
expect(Scheduler).toFlushAndYieldThrough(['b']);
346+
if (gate(flags => flags.allowConcurrentByDefault)) {
347+
expect(container.textContent).toEqual('a');
348+
} else {
349+
expect(container.textContent).toEqual('b');
350+
}
351+
});
352+
expect(container.textContent).toEqual('b');
353+
});
319354
});

packages/react-dom/src/client/ReactDOMRoot.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type RootOptions = {
2828
...
2929
},
3030
unstable_strictModeLevel?: number,
31+
unstable_concurrentUpdatesByDefault?: boolean,
3132
...
3233
};
3334

@@ -52,6 +53,7 @@ import {
5253
} from 'react-reconciler/src/ReactFiberReconciler';
5354
import invariant from 'shared/invariant';
5455
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
56+
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
5557

5658
function ReactDOMRoot(container: Container, options: void | RootOptions) {
5759
this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
@@ -126,12 +128,21 @@ function createRootImpl(
126128
? options.unstable_strictModeLevel
127129
: null;
128130

131+
let concurrentUpdatesByDefaultOverride = null;
132+
if (allowConcurrentByDefault) {
133+
concurrentUpdatesByDefaultOverride =
134+
options != null && options.unstable_concurrentUpdatesByDefault != null
135+
? options.unstable_concurrentUpdatesByDefault
136+
: null;
137+
}
138+
129139
const root = createContainer(
130140
container,
131141
tag,
132142
hydrate,
133143
hydrationCallbacks,
134144
strictModeLevelOverride,
145+
concurrentUpdatesByDefaultOverride,
135146
);
136147
markContainerAsRoot(root.current, container);
137148

packages/react-native-renderer/src/ReactFabric.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ function render(
207207
if (!root) {
208208
// TODO (bvaughn): If we decide to keep the wrapper component,
209209
// We could create a wrapper for containerTag as well to reduce special casing.
210-
root = createContainer(containerTag, LegacyRoot, false, null, null);
210+
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
211211
roots.set(containerTag, root);
212212
}
213213
updateContainer(element, root, null, callback);

packages/react-native-renderer/src/ReactNativeRenderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function render(
203203
if (!root) {
204204
// TODO (bvaughn): If we decide to keep the wrapper component,
205205
// We could create a wrapper for containerTag as well to reduce special casing.
206-
root = createContainer(containerTag, LegacyRoot, false, null, null);
206+
root = createContainer(containerTag, LegacyRoot, false, null, null, null);
207207
roots.set(containerTag, root);
208208
}
209209
updateContainer(element, root, null, callback);

packages/react-reconciler/src/ReactFiber.new.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
enableStrictEffects,
2525
enableProfilerTimer,
2626
enableScopeAPI,
27+
enableSyncDefaultUpdates,
28+
allowConcurrentByDefault,
2729
} from 'shared/ReactFeatureFlags';
2830
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
2931
import {ConcurrentRoot} from './ReactRootTags';
@@ -68,6 +70,7 @@ import {
6870
ProfileMode,
6971
StrictLegacyMode,
7072
StrictEffectsMode,
73+
ConcurrentUpdatesByDefaultMode,
7174
} from './ReactTypeOfMode';
7275
import {
7376
REACT_FORWARD_REF_TYPE,
@@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
420423
export function createHostRootFiber(
421424
tag: RootTag,
422425
strictModeLevelOverride: null | number,
426+
concurrentUpdatesByDefaultOverride: null | boolean,
423427
): Fiber {
424428
let mode;
425429
if (tag === ConcurrentRoot) {
@@ -440,6 +444,15 @@ export function createHostRootFiber(
440444
mode |= StrictLegacyMode;
441445
}
442446
}
447+
if (
448+
// We only use this flag for our repo tests to check both behaviors.
449+
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
450+
!enableSyncDefaultUpdates ||
451+
// Only for internal experiments.
452+
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
453+
) {
454+
mode |= ConcurrentUpdatesByDefaultMode;
455+
}
443456
} else {
444457
mode = NoMode;
445458
}

packages/react-reconciler/src/ReactFiber.old.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
enableStrictEffects,
2525
enableProfilerTimer,
2626
enableScopeAPI,
27+
enableSyncDefaultUpdates,
28+
allowConcurrentByDefault,
2729
} from 'shared/ReactFeatureFlags';
2830
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
2931
import {ConcurrentRoot} from './ReactRootTags';
@@ -68,6 +70,7 @@ import {
6870
ProfileMode,
6971
StrictLegacyMode,
7072
StrictEffectsMode,
73+
ConcurrentUpdatesByDefaultMode,
7174
} from './ReactTypeOfMode';
7275
import {
7376
REACT_FORWARD_REF_TYPE,
@@ -420,6 +423,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
420423
export function createHostRootFiber(
421424
tag: RootTag,
422425
strictModeLevelOverride: null | number,
426+
concurrentUpdatesByDefaultOverride: null | boolean,
423427
): Fiber {
424428
let mode;
425429
if (tag === ConcurrentRoot) {
@@ -440,6 +444,15 @@ export function createHostRootFiber(
440444
mode |= StrictLegacyMode;
441445
}
442446
}
447+
if (
448+
// We only use this flag for our repo tests to check both behaviors.
449+
// TODO: Flip this flag and rename it something like "forceConcurrentByDefaultForTesting"
450+
!enableSyncDefaultUpdates ||
451+
// Only for internal experiments.
452+
(allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
453+
) {
454+
mode |= ConcurrentUpdatesByDefaultMode;
455+
}
443456
} else {
444457
mode = NoMode;
445458
}

packages/react-reconciler/src/ReactFiberLane.new.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import {
2020
enableCache,
2121
enableSchedulingProfiler,
2222
enableUpdaterTracking,
23-
enableSyncDefaultUpdates,
23+
allowConcurrentByDefault,
2424
} from 'shared/ReactFeatureFlags';
2525
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
26+
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
2627

2728
// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
2829
// If those values are changed that package should be rebuilt and redeployed.
@@ -253,11 +254,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
253254
}
254255

255256
if (
256-
// TODO: Check for root override, once that lands
257-
enableSyncDefaultUpdates &&
258-
(nextLanes & InputContinuousLane) !== NoLanes
257+
allowConcurrentByDefault &&
258+
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
259259
) {
260-
// When updates are sync by default, we entangle continous priority updates
260+
// Do nothing, use the lanes as they were assigned.
261+
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
262+
// When updates are sync by default, we entangle continuous priority updates
261263
// and default updates, so they render in the same batch. The only reason
262264
// they use separate lanes is because continuous updates should interrupt
263265
// transitions, but default updates should not.
@@ -459,17 +461,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
459461
// finish rendering without yielding execution.
460462
return false;
461463
}
462-
if (enableSyncDefaultUpdates) {
463-
const SyncDefaultLanes =
464-
InputContinuousHydrationLane |
465-
InputContinuousLane |
466-
DefaultHydrationLane |
467-
DefaultLane;
468-
// TODO: Check for root override, once that lands
469-
return (lanes & SyncDefaultLanes) === NoLanes;
470-
} else {
464+
465+
if (
466+
allowConcurrentByDefault &&
467+
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
468+
) {
469+
// Concurrent updates by default always use time slicing.
471470
return true;
472471
}
472+
473+
const SyncDefaultLanes =
474+
InputContinuousHydrationLane |
475+
InputContinuousLane |
476+
DefaultHydrationLane |
477+
DefaultLane;
478+
return (lanes & SyncDefaultLanes) === NoLanes;
473479
}
474480

475481
export function isTransitionLane(lane: Lane) {

packages/react-reconciler/src/ReactFiberLane.old.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import {
2020
enableCache,
2121
enableSchedulingProfiler,
2222
enableUpdaterTracking,
23-
enableSyncDefaultUpdates,
23+
allowConcurrentByDefault,
2424
} from 'shared/ReactFeatureFlags';
2525
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
26+
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
2627

2728
// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
2829
// If those values are changed that package should be rebuilt and redeployed.
@@ -253,11 +254,12 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
253254
}
254255

255256
if (
256-
// TODO: Check for root override, once that lands
257-
enableSyncDefaultUpdates &&
258-
(nextLanes & InputContinuousLane) !== NoLanes
257+
allowConcurrentByDefault &&
258+
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
259259
) {
260-
// When updates are sync by default, we entangle continous priority updates
260+
// Do nothing, use the lanes as they were assigned.
261+
} else if ((nextLanes & InputContinuousLane) !== NoLanes) {
262+
// When updates are sync by default, we entangle continuous priority updates
261263
// and default updates, so they render in the same batch. The only reason
262264
// they use separate lanes is because continuous updates should interrupt
263265
// transitions, but default updates should not.
@@ -459,17 +461,21 @@ export function shouldTimeSlice(root: FiberRoot, lanes: Lanes) {
459461
// finish rendering without yielding execution.
460462
return false;
461463
}
462-
if (enableSyncDefaultUpdates) {
463-
const SyncDefaultLanes =
464-
InputContinuousHydrationLane |
465-
InputContinuousLane |
466-
DefaultHydrationLane |
467-
DefaultLane;
468-
// TODO: Check for root override, once that lands
469-
return (lanes & SyncDefaultLanes) === NoLanes;
470-
} else {
464+
465+
if (
466+
allowConcurrentByDefault &&
467+
(root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode
468+
) {
469+
// Concurrent updates by default always use time slicing.
471470
return true;
472471
}
472+
473+
const SyncDefaultLanes =
474+
InputContinuousHydrationLane |
475+
InputContinuousLane |
476+
DefaultHydrationLane |
477+
DefaultLane;
478+
return (lanes & SyncDefaultLanes) === NoLanes;
473479
}
474480

475481
export function isTransitionLane(lane: Lane) {

packages/react-reconciler/src/ReactFiberReconciler.new.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,15 @@ export function createContainer(
249249
hydrate: boolean,
250250
hydrationCallbacks: null | SuspenseHydrationCallbacks,
251251
strictModeLevelOverride: null | number,
252+
concurrentUpdatesByDefaultOverride: null | boolean,
252253
): OpaqueRoot {
253254
return createFiberRoot(
254255
containerInfo,
255256
tag,
256257
hydrate,
257258
hydrationCallbacks,
258259
strictModeLevelOverride,
260+
concurrentUpdatesByDefaultOverride,
259261
);
260262
}
261263

packages/react-reconciler/src/ReactFiberReconciler.old.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,15 @@ export function createContainer(
249249
hydrate: boolean,
250250
hydrationCallbacks: null | SuspenseHydrationCallbacks,
251251
strictModeLevelOverride: null | number,
252+
concurrentUpdatesByDefaultOverride: null | boolean,
252253
): OpaqueRoot {
253254
return createFiberRoot(
254255
containerInfo,
255256
tag,
256257
hydrate,
257258
hydrationCallbacks,
258259
strictModeLevelOverride,
260+
concurrentUpdatesByDefaultOverride,
259261
);
260262
}
261263

packages/react-reconciler/src/ReactFiberRoot.new.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function createFiberRoot(
9999
hydrate: boolean,
100100
hydrationCallbacks: null | SuspenseHydrationCallbacks,
101101
strictModeLevelOverride: null | number,
102+
concurrentUpdatesByDefaultOverride: null | boolean,
102103
): FiberRoot {
103104
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
104105
if (enableSuspenseCallback) {
@@ -107,7 +108,11 @@ export function createFiberRoot(
107108

108109
// Cyclic construction. This cheats the type system right now because
109110
// stateNode is any.
110-
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
111+
const uninitializedFiber = createHostRootFiber(
112+
tag,
113+
strictModeLevelOverride,
114+
concurrentUpdatesByDefaultOverride,
115+
);
111116
root.current = uninitializedFiber;
112117
uninitializedFiber.stateNode = root;
113118

packages/react-reconciler/src/ReactFiberRoot.old.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function createFiberRoot(
9999
hydrate: boolean,
100100
hydrationCallbacks: null | SuspenseHydrationCallbacks,
101101
strictModeLevelOverride: null | number,
102+
concurrentUpdatesByDefaultOverride: null | boolean,
102103
): FiberRoot {
103104
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
104105
if (enableSuspenseCallback) {
@@ -107,7 +108,11 @@ export function createFiberRoot(
107108

108109
// Cyclic construction. This cheats the type system right now because
109110
// stateNode is any.
110-
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
111+
const uninitializedFiber = createHostRootFiber(
112+
tag,
113+
strictModeLevelOverride,
114+
concurrentUpdatesByDefaultOverride,
115+
);
111116
root.current = uninitializedFiber;
112117
uninitializedFiber.stateNode = root;
113118

0 commit comments

Comments
 (0)