diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
index 25a560c5c32af..c5e6e4898ebe9 100644
--- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
@@ -13,6 +13,7 @@ let React = require('react');
let ReactDOM = require('react-dom');
let ReactDOMServer = require('react-dom/server');
let Scheduler = require('scheduler');
+let act;
describe('ReactDOMRoot', () => {
let container;
@@ -24,6 +25,7 @@ describe('ReactDOMRoot', () => {
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Scheduler = require('scheduler');
+ act = require('react-dom/test-utils').unstable_concurrentAct;
});
if (!__EXPERIMENTAL__) {
@@ -316,4 +318,33 @@ describe('ReactDOMRoot', () => {
{withoutStack: true},
);
});
+
+ // @gate experimental
+ it('opts-in to concurrent default updates', async () => {
+ const root = ReactDOM.unstable_createRoot(container, {
+ unstable_concurrentUpdatesByDefault: true,
+ });
+
+ function Foo({value}) {
+ Scheduler.unstable_yieldValue(value);
+ return
{value}
;
+ }
+
+ await act(async () => {
+ root.render();
+ });
+
+ expect(container.textContent).toEqual('a');
+
+ await act(async () => {
+ root.render();
+
+ expect(Scheduler).toHaveYielded(['a']);
+ expect(container.textContent).toEqual('a');
+
+ expect(Scheduler).toFlushAndYieldThrough(['b']);
+ expect(container.textContent).toEqual('a');
+ });
+ expect(container.textContent).toEqual('b');
+ });
});
diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js
index 56532d5d67488..068a080a1cef1 100644
--- a/packages/react-dom/src/client/ReactDOMRoot.js
+++ b/packages/react-dom/src/client/ReactDOMRoot.js
@@ -28,6 +28,7 @@ export type RootOptions = {
...
},
unstable_strictModeLevel?: number,
+ unstable_concurrentUpdatesByDefault?: boolean,
...
};
@@ -125,6 +126,10 @@ function createRootImpl(
options != null && options.unstable_strictModeLevel != null
? options.unstable_strictModeLevel
: null;
+ const concurrentUpdatesByDefaultOverride =
+ options != null && options.unstable_concurrentUpdatesByDefault != null
+ ? options.unstable_concurrentUpdatesByDefault
+ : null;
const root = createContainer(
container,
@@ -132,6 +137,7 @@ function createRootImpl(
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
+ concurrentUpdatesByDefaultOverride,
);
markContainerAsRoot(root.current, container);
diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js
index 95a5340b18730..2401cd04cf3fd 100644
--- a/packages/react-reconciler/src/ReactFiber.new.js
+++ b/packages/react-reconciler/src/ReactFiber.new.js
@@ -68,6 +68,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
+ ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
@@ -420,6 +421,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
@@ -440,6 +442,9 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
+ if (concurrentUpdatesByDefaultOverride) {
+ mode |= ConcurrentUpdatesByDefaultMode;
+ }
} else {
mode = NoMode;
}
diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js
index 159557e86a166..7228b33710b66 100644
--- a/packages/react-reconciler/src/ReactFiber.old.js
+++ b/packages/react-reconciler/src/ReactFiber.old.js
@@ -68,6 +68,7 @@ import {
ProfileMode,
StrictLegacyMode,
StrictEffectsMode,
+ ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
REACT_FORWARD_REF_TYPE,
@@ -420,6 +421,7 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
@@ -440,6 +442,9 @@ export function createHostRootFiber(
mode |= StrictLegacyMode;
}
}
+ if (concurrentUpdatesByDefaultOverride) {
+ mode |= ConcurrentUpdatesByDefaultMode;
+ }
} else {
mode = NoMode;
}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js
index 4572e4d9de4ba..b782062100393 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.new.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js
@@ -249,6 +249,7 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
@@ -256,6 +257,7 @@ export function createContainer(
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
+ concurrentUpdatesByDefaultOverride,
);
}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js
index abd5b9094c59c..c7df1b8c57462 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.old.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js
@@ -249,6 +249,7 @@ export function createContainer(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): OpaqueRoot {
return createFiberRoot(
containerInfo,
@@ -256,6 +257,7 @@ export function createContainer(
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
+ concurrentUpdatesByDefaultOverride,
);
}
diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js
index 9201a35980753..4f92bc2f05db6 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.new.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.new.js
@@ -105,6 +105,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
@@ -113,7 +114,11 @@ export function createFiberRoot(
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
- const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
+ const uninitializedFiber = createHostRootFiber(
+ tag,
+ strictModeLevelOverride,
+ concurrentUpdatesByDefaultOverride,
+ );
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js
index f2968b314ecae..a69ca37e0d4a4 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.old.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.old.js
@@ -105,6 +105,7 @@ export function createFiberRoot(
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
+ concurrentUpdatesByDefaultOverride: null | boolean,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
@@ -113,7 +114,11 @@ export function createFiberRoot(
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
- const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
+ const uninitializedFiber = createHostRootFiber(
+ tag,
+ strictModeLevelOverride,
+ concurrentUpdatesByDefaultOverride,
+ );
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index df1b2653e204a..dfe7993bc3d93 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -107,6 +107,7 @@ import {
StrictLegacyMode,
ProfileMode,
ConcurrentMode,
+ ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
HostRoot,
@@ -440,6 +441,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
if (updateLane !== NoLane) {
if (
enableSyncDefaultUpdates &&
+ (mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(updateLane === InputContinuousLane ||
updateLane === InputContinuousHydrationLane)
) {
@@ -457,6 +459,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
const eventLane: Lane = (getCurrentEventPriority(): any);
if (
enableSyncDefaultUpdates &&
+ (mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(eventLane === InputContinuousLane ||
eventLane === InputContinuousHydrationLane)
) {
@@ -716,6 +719,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
let newCallbackNode;
if (
enableSyncDefaultUpdates &&
+ (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(newCallbackPriority === DefaultLane ||
newCallbackPriority === DefaultHydrationLane)
) {
@@ -1058,7 +1062,11 @@ function performSyncWorkOnRoot(root) {
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
- if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) {
+ if (
+ enableSyncDefaultUpdates &&
+ (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
+ !includesSomeLane(lanes, SyncLane)
+ ) {
finishConcurrentRender(root, exitStatus, lanes);
} else {
commitRoot(root);
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index 07f8a38b8bc0a..185f4de1f8065 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -107,6 +107,7 @@ import {
StrictLegacyMode,
ProfileMode,
ConcurrentMode,
+ ConcurrentUpdatesByDefaultMode,
} from './ReactTypeOfMode';
import {
HostRoot,
@@ -440,6 +441,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
if (updateLane !== NoLane) {
if (
enableSyncDefaultUpdates &&
+ (mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(updateLane === InputContinuousLane ||
updateLane === InputContinuousHydrationLane)
) {
@@ -457,6 +459,7 @@ export function requestUpdateLane(fiber: Fiber): Lane {
const eventLane: Lane = (getCurrentEventPriority(): any);
if (
enableSyncDefaultUpdates &&
+ (mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(eventLane === InputContinuousLane ||
eventLane === InputContinuousHydrationLane)
) {
@@ -716,6 +719,7 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
let newCallbackNode;
if (
enableSyncDefaultUpdates &&
+ (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
(newCallbackPriority === DefaultLane ||
newCallbackPriority === DefaultHydrationLane)
) {
@@ -1058,7 +1062,11 @@ function performSyncWorkOnRoot(root) {
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
- if (enableSyncDefaultUpdates && !includesSomeLane(lanes, SyncLane)) {
+ if (
+ enableSyncDefaultUpdates &&
+ (root.current.mode & ConcurrentUpdatesByDefaultMode) === NoMode &&
+ !includesSomeLane(lanes, SyncLane)
+ ) {
finishConcurrentRender(root, exitStatus, lanes);
} else {
commitRoot(root);
diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js
index 466363fabd4e8..f16d2c51b76c0 100644
--- a/packages/react-reconciler/src/ReactTypeOfMode.js
+++ b/packages/react-reconciler/src/ReactTypeOfMode.js
@@ -9,10 +9,11 @@
export type TypeOfMode = number;
-export const NoMode = /* */ 0b000000;
+export const NoMode = /* */ 0b000000;
// TODO: Remove ConcurrentMode by reading from the root tag instead
-export const ConcurrentMode = /* */ 0b000001;
-export const ProfileMode = /* */ 0b000010;
-export const DebugTracingMode = /* */ 0b000100;
-export const StrictLegacyMode = /* */ 0b001000;
-export const StrictEffectsMode = /* */ 0b010000;
+export const ConcurrentMode = /* */ 0b000001;
+export const ProfileMode = /* */ 0b000010;
+export const DebugTracingMode = /* */ 0b000100;
+export const StrictLegacyMode = /* */ 0b001000;
+export const StrictEffectsMode = /* */ 0b010000;
+export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000;
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index 5c74c6e9e310c..6497f5efd77c3 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -58,6 +58,7 @@ type TestRendererOptions = {
createNodeMock: (element: React$Element) => any,
unstable_isConcurrent: boolean,
unstable_strictModeLevel: number,
+ unstable_concurrentUpdatesByDefault: boolean,
...
};
@@ -436,6 +437,7 @@ function create(element: React$Element, options: TestRendererOptions) {
let createNodeMock = defaultTestOptions.createNodeMock;
let isConcurrent = false;
let strictModeLevel = null;
+ let concurrentUpdatesByDefault = null;
if (typeof options === 'object' && options !== null) {
if (typeof options.createNodeMock === 'function') {
createNodeMock = options.createNodeMock;
@@ -446,6 +448,9 @@ function create(element: React$Element, options: TestRendererOptions) {
if (options.unstable_strictModeLevel !== undefined) {
strictModeLevel = options.unstable_strictModeLevel;
}
+ if (options.unstable_concurrentUpdatesByDefault !== undefined) {
+ concurrentUpdatesByDefault = options.unstable_concurrentUpdatesByDefault;
+ }
}
let container = {
children: [],
@@ -458,6 +463,7 @@ function create(element: React$Element, options: TestRendererOptions) {
false,
null,
strictModeLevel,
+ concurrentUpdatesByDefault,
);
invariant(root != null, 'something went wrong');
updateContainer(element, root, null, null);