diff --git a/fixtures/dom/src/__tests__/nested-act-test.js b/fixtures/dom/src/__tests__/nested-act-test.js
index 4a39a0ea98f7f..3219706e3fff6 100644
--- a/fixtures/dom/src/__tests__/nested-act-test.js
+++ b/fixtures/dom/src/__tests__/nested-act-test.js
@@ -48,7 +48,7 @@ describe('unmocked scheduler', () => {
TestAct(() => {
TestRenderer.create();
});
- expect(log).toEqual(['called']);
+ expect(log).toEqual([]);
});
expect(log).toEqual(['called']);
});
diff --git a/fixtures/dom/src/__tests__/wrong-act-test.js b/fixtures/dom/src/__tests__/wrong-act-test.js
deleted file mode 100644
index 05f1853f8a2c7..0000000000000
--- a/fixtures/dom/src/__tests__/wrong-act-test.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @emails react-core
- */
-
-let React;
-let ReactDOM;
-let ReactART;
-let TestUtils;
-let ARTSVGMode;
-let ARTCurrentMode;
-let TestRenderer;
-let ARTTest;
-
-global.__DEV__ = process.env.NODE_ENV !== 'production';
-global.__EXPERIMENTAL__ = process.env.RELEASE_CHANNEL === 'experimental';
-
-expect.extend(require('../toWarnDev'));
-
-function App(props) {
- return 'hello world';
-}
-
-beforeEach(() => {
- jest.resetModules();
- React = require('react');
- ReactDOM = require('react-dom');
- TestUtils = require('react-dom/test-utils');
- ReactART = require('react-art');
- ARTSVGMode = require('art/modes/svg');
- ARTCurrentMode = require('art/modes/current');
- TestRenderer = require('react-test-renderer');
-
- ARTCurrentMode.setCurrent(ARTSVGMode);
-
- ARTTest = function ARTTestComponent(props) {
- return (
-
-
-
-
- M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
- h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
- h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
-
-
-
- );
- };
-});
-
-it("doesn't warn when you use the right act + renderer: dom", () => {
- TestUtils.act(() => {
- ReactDOM.render(, document.createElement('div'));
- });
-});
-
-it("doesn't warn when you use the right act + renderer: test", () => {
- TestRenderer.act(() => {
- TestRenderer.create();
- });
-});
-
-it('resets correctly across renderers', async () => {
- function Effecty() {
- React.useEffect(() => {}, []);
- return null;
- }
- await TestUtils.act(async () => {
- TestRenderer.act(() => {});
- expect(() => {
- TestRenderer.create();
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
-});
-
-it('warns when using the wrong act version - test + dom: render', () => {
- expect(() => {
- TestRenderer.act(() => {
- ReactDOM.render(, document.createElement('div'));
- });
- }).toWarnDev(
- [
- 'ReactDOM.render is no longer supported in React 18.',
- "It looks like you're using the wrong act()",
- ],
- {
- withoutStack: true,
- }
- );
-});
-
-it('warns when using the wrong act version - test + dom: updates', () => {
- let setCtr;
- function Counter(props) {
- const [ctr, _setCtr] = React.useState(0);
- setCtr = _setCtr;
- return ctr;
- }
- ReactDOM.render(, document.createElement('div'));
- expect(() => {
- TestRenderer.act(() => {
- setCtr(1);
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
-});
-
-it('warns when using the wrong act version - dom + test: .create()', () => {
- expect(() => {
- TestUtils.act(() => {
- TestRenderer.create();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
-});
-
-it('warns when using the wrong act version - dom + test: .update()', () => {
- const root = TestRenderer.create();
- expect(() => {
- TestUtils.act(() => {
- root.update();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
-});
-
-it('warns when using the wrong act version - dom + test: updates', () => {
- let setCtr;
- function Counter(props) {
- const [ctr, _setCtr] = React.useState(0);
- setCtr = _setCtr;
- return ctr;
- }
- TestRenderer.create();
- expect(() => {
- TestUtils.act(() => {
- setCtr(1);
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
-});
-
-it('does not warn when nesting react-act inside react-dom', () => {
- TestUtils.act(() => {
- ReactDOM.render(, document.createElement('div'));
- });
-});
-
-it('does not warn when nesting react-act inside react-test-renderer', () => {
- TestRenderer.act(() => {
- TestRenderer.create();
- });
-});
-
-it("doesn't warn if you use nested acts from different renderers", () => {
- TestRenderer.act(() => {
- TestUtils.act(() => {
- TestRenderer.create();
- });
- });
-});
-
-if (__EXPERIMENTAL__) {
- it('warns when using createRoot() + .render', () => {
- const root = ReactDOM.createRoot(document.createElement('div'));
- expect(() => {
- TestRenderer.act(() => {
- root.render();
- });
- }).toWarnDev(
- [
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked',
- "It looks like you're using the wrong act()",
- ],
- {
- withoutStack: true,
- }
- );
- });
-}
diff --git a/packages/react-dom/src/__tests__/ReactDOMTestSelectors-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMTestSelectors-test.internal.js
index 24c3978566a01..e9b0cc6ea0e5b 100644
--- a/packages/react-dom/src/__tests__/ReactDOMTestSelectors-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMTestSelectors-test.internal.js
@@ -33,7 +33,7 @@ describe('ReactDOMTestSelectors', () => {
React = require('react');
const ReactDOM = require('react-dom/testing');
- act = ReactDOM.act;
+ act = React.unstable_act;
createComponentSelector = ReactDOM.createComponentSelector;
createHasPseudoClassSelector = ReactDOM.createHasPseudoClassSelector;
createRoleSelector = ReactDOM.createRoleSelector;
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
index 2dabcfc5281b7..0e10207b0bb04 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
@@ -354,32 +354,6 @@ function runActTests(label, render, unmount, rerender) {
expect(container.innerHTML).toBe('2');
});
});
-
- // @gate __DEV__
- it('warns if you return a value inside act', () => {
- expect(() => act(() => null)).toErrorDev(
- [
- 'The callback passed to act(...) function must return undefined, or a Promise.',
- ],
- {withoutStack: true},
- );
- expect(() => act(() => 123)).toErrorDev(
- [
- 'The callback passed to act(...) function must return undefined, or a Promise.',
- ],
- {withoutStack: true},
- );
- });
-
- // @gate __DEV__
- it('warns if you try to await a sync .act call', () => {
- expect(() => act(() => {}).then(() => {})).toErrorDev(
- [
- 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
- ],
- {withoutStack: true},
- );
- });
});
describe('asynchronous tests', () => {
@@ -401,15 +375,17 @@ function runActTests(label, render, unmount, rerender) {
await act(async () => {
render(, container);
- // flush a little to start the timer
- expect(Scheduler).toFlushAndYield([]);
+ });
+ expect(container.innerHTML).toBe('0');
+ // Flush the pending timers
+ await act(async () => {
await sleep(100);
});
expect(container.innerHTML).toBe('1');
});
// @gate __DEV__
- it('flushes microtasks before exiting', async () => {
+ it('flushes microtasks before exiting (async function)', async () => {
function App() {
const [ctr, setCtr] = React.useState(0);
async function someAsyncFunction() {
@@ -431,6 +407,31 @@ function runActTests(label, render, unmount, rerender) {
expect(container.innerHTML).toEqual('1');
});
+ // @gate __DEV__
+ it('flushes microtasks before exiting (sync function)', async () => {
+ // Same as previous test, but the callback passed to `act` is not itself
+ // an async function.
+ function App() {
+ const [ctr, setCtr] = React.useState(0);
+ async function someAsyncFunction() {
+ // queue a bunch of promises to be sure they all flush
+ await null;
+ await null;
+ await null;
+ setCtr(1);
+ }
+ React.useEffect(() => {
+ someAsyncFunction();
+ }, []);
+ return ctr;
+ }
+
+ await act(() => {
+ render(, container);
+ });
+ expect(container.innerHTML).toEqual('1');
+ });
+
// @gate __DEV__
it('warns if you do not await an act call', async () => {
spyOnDevAndProd(console, 'error');
@@ -461,7 +462,13 @@ function runActTests(label, render, unmount, rerender) {
await sleep(150);
if (__DEV__) {
- expect(console.error).toHaveBeenCalledTimes(1);
+ expect(console.error).toHaveBeenCalledTimes(2);
+ expect(console.error.calls.argsFor(0)[0]).toMatch(
+ 'You seem to have overlapping act() calls',
+ );
+ expect(console.error.calls.argsFor(1)[0]).toMatch(
+ 'You seem to have overlapping act() calls',
+ );
}
});
diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js
deleted file mode 100644
index 501b01336b1b3..0000000000000
--- a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @emails react-core
- */
-
-let React;
-let ReactDOM;
-let ReactFeatureFlags;
-
-function App() {
- return null;
-}
-
-beforeEach(() => {
- jest.resetModules();
- jest.unmock('scheduler');
- React = require('react');
- ReactDOM = require('react-dom');
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.warnAboutUnmockedScheduler = true;
-});
-
-afterEach(() => {
- ReactFeatureFlags.warnAboutUnmockedScheduler = false;
-});
-
-it('should warn in legacy mode', () => {
- expect(() => {
- ReactDOM.render(, document.createElement('div'));
- }).toErrorDev(
- ['Starting from React v18, the "scheduler" module will need to be mocked'],
- {withoutStack: true},
- );
- // does not warn twice
- expect(() => {
- ReactDOM.render(, document.createElement('div'));
- }).toErrorDev([]);
-});
-
-it('does not warn if Scheduler is mocked', () => {
- jest.resetModules();
- jest.mock('scheduler', () => require('scheduler/unstable_mock'));
- React = require('react');
- ReactDOM = require('react-dom');
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.warnAboutUnmockedScheduler = true;
-
- // This should not warn
- expect(() => {
- ReactDOM.render(, document.createElement('div'));
- }).toErrorDev([]);
-});
diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
deleted file mode 100644
index 960154dbea253..0000000000000
--- a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @emails react-core
- */
-
-let React;
-let ReactDOM;
-
-function App() {
- return null;
-}
-
-beforeEach(() => {
- jest.resetModules();
- jest.unmock('scheduler');
- React = require('react');
- ReactDOM = require('react-dom');
-});
-
-it('does not warn when rendering in legacy mode', () => {
- expect(() => {
- ReactDOM.render(, document.createElement('div'));
- }).toErrorDev([]);
-});
-
-it('should warn when rendering in concurrent mode', () => {
- expect(() => {
- ReactDOM.createRoot(document.createElement('div')).render();
- }).toErrorDev(
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers.',
- {withoutStack: true},
- );
- // does not warn twice
- expect(() => {
- ReactDOM.createRoot(document.createElement('div')).render();
- }).toErrorDev([]);
-});
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index eade4ea474c77..e603121d82c08 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -29,7 +29,6 @@ import {
flushControlled,
injectIntoDevTools,
IsThisRendererActing,
- act,
attemptSynchronousHydration,
attemptDiscreteHydration,
attemptContinuousHydration,
@@ -163,8 +162,8 @@ const Internals = {
getFiberCurrentPropsFromNode,
enqueueStateRestore,
restoreStateIfNeeded,
+ batchedUpdates,
],
- act,
// TODO: Temporary. Only used by our internal version of `act. Will remove.
IsThisRendererActing,
};
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index 69c1a6afcff3a..e721dd30e7a34 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -34,8 +34,14 @@ const getNodeFromInstance = EventInternals[1];
const getFiberCurrentPropsFromNode = EventInternals[2];
const enqueueStateRestore = EventInternals[3];
const restoreStateIfNeeded = EventInternals[4];
+const batchedUpdates = EventInternals[5];
-const act = SecretInternals.act;
+const act_notBatchedInLegacyMode = React.unstable_act;
+function act(callback) {
+ return act_notBatchedInLegacyMode(() => {
+ return batchedUpdates(callback);
+ });
+}
function Event(suffix) {}
diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js
index 3adc786eed5fa..6afe4af8aa49e 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js
@@ -20,7 +20,7 @@ const IsThisRendererActing = SecretInternals.IsThisRendererActing;
const batchedUpdates = ReactDOM.unstable_batchedUpdates;
-const {IsSomeRendererActing} = ReactSharedInternals;
+const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
// This version of `act` is only used by our tests. Unlike the public version
// of `act`, it's designed to work identically in both production and
@@ -28,6 +28,8 @@ const {IsSomeRendererActing} = ReactSharedInternals;
// version, too, since our constraints in our test suite are not the same as
// those of developers using React — we're testing React itself, as opposed to
// building an app with React.
+// TODO: Replace the internal "concurrent" implementations of `act` with a
+// single shared module.
let actingUpdatesScopeDepth = 0;
@@ -50,8 +52,14 @@ export function unstable_concurrentAct(scope: () => Thenable | void) {
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
actingUpdatesScopeDepth++;
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = true;
+ }
const unwind = () => {
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = false;
+ }
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
diff --git a/packages/react-dom/testing.classic.fb.js b/packages/react-dom/testing.classic.fb.js
index 923ba78f09d1a..a127b63f8c8f6 100644
--- a/packages/react-dom/testing.classic.fb.js
+++ b/packages/react-dom/testing.classic.fb.js
@@ -9,7 +9,6 @@
export * from './index.classic.fb.js';
export {
- act,
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
diff --git a/packages/react-dom/testing.experimental.js b/packages/react-dom/testing.experimental.js
index 4d3f2d2a7f4f2..f994d83fa7d22 100644
--- a/packages/react-dom/testing.experimental.js
+++ b/packages/react-dom/testing.experimental.js
@@ -8,4 +8,3 @@
*/
export * from './index.experimental.js';
-export {act} from 'react-reconciler/src/ReactFiberReconciler';
diff --git a/packages/react-dom/testing.js b/packages/react-dom/testing.js
index d66124f67d83e..ef4ac5899dfd8 100644
--- a/packages/react-dom/testing.js
+++ b/packages/react-dom/testing.js
@@ -9,7 +9,6 @@
export * from './index.js';
export {
- act,
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
diff --git a/packages/react-dom/testing.modern.fb.js b/packages/react-dom/testing.modern.fb.js
index 7f22bb849e7c6..738ae3a3710f8 100644
--- a/packages/react-dom/testing.modern.fb.js
+++ b/packages/react-dom/testing.modern.fb.js
@@ -9,7 +9,6 @@
export * from './index.modern.fb.js';
export {
- act,
createComponentSelector,
createHasPseudoClassSelector,
createRoleSelector,
diff --git a/packages/react-dom/testing.stable.js b/packages/react-dom/testing.stable.js
index 47161fadd7f88..044b15bde9c4d 100644
--- a/packages/react-dom/testing.stable.js
+++ b/packages/react-dom/testing.stable.js
@@ -8,4 +8,3 @@
*/
export * from './index.stable.js';
-export {act} from 'react-reconciler/src/ReactFiberReconciler';
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 9d2c87621bcef..59cea7cd5d971 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -31,7 +31,7 @@ import {
import ReactSharedInternals from 'shared/ReactSharedInternals';
import enqueueTask from 'shared/enqueueTask';
-const {IsSomeRendererActing} = ReactSharedInternals;
+const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
type Container = {
rootID: string,
@@ -1048,6 +1048,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
// version, too, since our constraints in our test suite are not the same as
// those of developers using React — we're testing React itself, as opposed to
// building an app with React.
+ // TODO: Replace the internal "concurrent" implementations of `act` with a
+ // single shared module.
const {batchedUpdates, IsThisRendererActing} = NoopRenderer;
let actingUpdatesScopeDepth = 0;
@@ -1071,8 +1073,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
actingUpdatesScopeDepth++;
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = true;
+ }
const unwind = () => {
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = false;
+ }
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js
index 394aa983174c4..b195a0cb57488 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.new.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.new.js
@@ -79,7 +79,6 @@ import {
requestEventTime,
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDev,
- warnIfNotScopedWithMatchingAct,
markSkippedUpdateLanes,
isInterleavedUpdate,
} from './ReactFiberWorkLoop.new';
@@ -2011,7 +2010,6 @@ function dispatchAction(
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
- warnIfNotScopedWithMatchingAct(fiber);
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js
index a5eac6836e283..7c51c43ad2e7e 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.old.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.old.js
@@ -79,7 +79,6 @@ import {
requestEventTime,
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDev,
- warnIfNotScopedWithMatchingAct,
markSkippedUpdateLanes,
isInterleavedUpdate,
} from './ReactFiberWorkLoop.old';
@@ -2011,7 +2010,6 @@ function dispatchAction(
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
- warnIfNotScopedWithMatchingAct(fiber);
warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index ff68770f09b06..6675ed78d5ffb 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -38,7 +38,6 @@ import {
shouldError as shouldError_old,
shouldSuspend as shouldSuspend_old,
injectIntoDevTools as injectIntoDevTools_old,
- act as act_old,
createPortal as createPortal_old,
createComponentSelector as createComponentSelector_old,
createHasPseudoClassSelector as createHasPseudoClassSelector_old,
@@ -79,7 +78,6 @@ import {
shouldError as shouldError_new,
shouldSuspend as shouldSuspend_new,
injectIntoDevTools as injectIntoDevTools_new,
- act as act_new,
createPortal as createPortal_new,
createComponentSelector as createComponentSelector_new,
createHasPseudoClassSelector as createHasPseudoClassSelector_new,
@@ -166,7 +164,6 @@ export const shouldSuspend = enableNewReconciler
export const injectIntoDevTools = enableNewReconciler
? injectIntoDevTools_new
: injectIntoDevTools_old;
-export const act = enableNewReconciler ? act_new : act_old;
export const createPortal = enableNewReconciler
? createPortal_new
: createPortal_old;
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js
index c53fabbb8e4ae..9e831157bf5fc 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.new.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js
@@ -60,10 +60,7 @@ import {
discreteUpdates,
flushDiscreteUpdates,
flushPassiveEffects,
- warnIfNotScopedWithMatchingAct,
- warnIfUnmockedScheduler,
IsThisRendererActing,
- act,
} from './ReactFiberWorkLoop.new';
import {
createUpdate,
@@ -272,13 +269,6 @@ export function updateContainer(
}
const current = container.current;
const eventTime = requestEventTime();
- if (__DEV__) {
- // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
- if ('undefined' !== typeof jest) {
- warnIfUnmockedScheduler(current);
- warnIfNotScopedWithMatchingAct(current);
- }
- }
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
@@ -348,7 +338,6 @@ export {
flushSync,
flushPassiveEffects,
IsThisRendererActing,
- act,
};
export function getPublicRootInstance(
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js
index 73d412bfcd1ae..8accca3deb359 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.old.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js
@@ -60,10 +60,7 @@ import {
discreteUpdates,
flushDiscreteUpdates,
flushPassiveEffects,
- warnIfNotScopedWithMatchingAct,
- warnIfUnmockedScheduler,
IsThisRendererActing,
- act,
} from './ReactFiberWorkLoop.old';
import {
createUpdate,
@@ -272,13 +269,6 @@ export function updateContainer(
}
const current = container.current;
const eventTime = requestEventTime();
- if (__DEV__) {
- // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
- if ('undefined' !== typeof jest) {
- warnIfUnmockedScheduler(current);
- warnIfNotScopedWithMatchingAct(current);
- }
- }
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
@@ -348,7 +338,6 @@ export {
flushSync,
flushPassiveEffects,
IsThisRendererActing,
- act,
};
export function getPublicRootInstance(
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index eb26348ca16e6..b4f889c569afe 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {Thenable, Wakeable} from 'shared/ReactTypes';
+import type {Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane.new';
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
@@ -24,7 +24,6 @@ import {
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableProfilerNestedUpdateScheduledHook,
- warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
enableDebugTracing,
enableSchedulingProfiler,
@@ -37,8 +36,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
import {
- scheduleCallback,
- cancelCallback,
+ // Aliased because `act` will override and push to an internal queue
+ scheduleCallback as Scheduler_scheduleCallback,
+ cancelCallback as Scheduler_cancelCallback,
shouldYield,
requestPaint,
now,
@@ -79,9 +79,6 @@ import {
markRenderStopped,
} from './SchedulingProfiler';
-// The scheduler is imported here *only* to detect whether it's been mocked
-import * as Scheduler from 'scheduler';
-
import {
resetAfterCommit,
scheduleTimeout,
@@ -238,16 +235,13 @@ import {
} from './ReactFiberDevToolsHook.new';
import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
-// Used by `act`
-import enqueueTask from 'shared/enqueueTask';
-
const ceil = Math.ceil;
const {
ReactCurrentDispatcher,
ReactCurrentOwner,
ReactCurrentBatchConfig,
- IsSomeRendererActing,
+ ReactCurrentActQueue,
} = ReactSharedInternals;
type ExecutionContext = number;
@@ -653,7 +647,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// Check if there's an existing task. We may be able to reuse it.
const existingCallbackPriority = root.callbackPriority;
- if (existingCallbackPriority === newCallbackPriority) {
+ if (
+ existingCallbackPriority === newCallbackPriority &&
+ // Special case related to `act`. If the currently scheduled task is a
+ // Scheduler task, rather than an `act` task, cancel it and re-scheduled
+ // on the `act` queue.
+ !(
+ __DEV__ &&
+ ReactCurrentActQueue.current !== null &&
+ existingCallbackNode !== fakeActCallbackNode
+ )
+ ) {
if (__DEV__) {
// If we're going to re-use an existing task, it needs to exist.
// Assume that discrete update microtasks are non-cancellable and null.
@@ -2781,42 +2785,35 @@ export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void {
}
}
-export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
+const fakeActCallbackNode = {};
+function scheduleCallback(priorityLevel, callback) {
if (__DEV__) {
- if (
- warnsIfNotActing === true &&
- IsSomeRendererActing.current === true &&
- IsThisRendererActing.current !== true
- ) {
- const previousFiber = ReactCurrentFiberCurrent;
- try {
- setCurrentDebugFiberInDEV(fiber);
- console.error(
- "It looks like you're using the wrong act() around your test interactions.\n" +
- 'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
- '// for react-dom:\n' +
- // Break up imports to avoid accidentally parsing them as dependencies.
- 'import {act} fr' +
- "om 'react-dom/test-utils';\n" +
- '// ...\n' +
- 'act(() => ...);\n\n' +
- '// for react-test-renderer:\n' +
- // Break up imports to avoid accidentally parsing them as dependencies.
- 'import TestRenderer fr' +
- "om 'react-test-renderer';\n" +
- 'const {act} = TestRenderer;\n' +
- '// ...\n' +
- 'act(() => ...);',
- );
- } finally {
- if (previousFiber) {
- setCurrentDebugFiberInDEV(fiber);
- } else {
- resetCurrentDebugFiberInDEV();
- }
- }
+ // If we're currently inside an `act` scope, bypass Scheduler and push to
+ // the `act` queue instead.
+ const actQueue = ReactCurrentActQueue.current;
+ if (actQueue !== null) {
+ actQueue.push(callback);
+ return fakeActCallbackNode;
+ } else {
+ return Scheduler_scheduleCallback(priorityLevel, callback);
}
+ } else {
+ // In production, always call Scheduler. This function will be stripped out.
+ return Scheduler_scheduleCallback(priorityLevel, callback);
+ }
+}
+
+function cancelCallback(callbackNode) {
+ if (__DEV__ && callbackNode === fakeActCallbackNode) {
+ return;
}
+ // In production, always call Scheduler. This function will be stripped out.
+ return Scheduler_cancelCallback(callbackNode);
+}
+
+function shouldForceFlushFallbacksInDEV() {
+ // Never force flush in production. This function should get stripped out.
+ return __DEV__ && ReactCurrentActQueue.current !== null;
}
export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
@@ -2824,8 +2821,13 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
if (
warnsIfNotActing === true &&
(fiber.mode & StrictLegacyMode) !== NoMode &&
- IsSomeRendererActing.current === false &&
- IsThisRendererActing.current === false
+ ReactCurrentActQueue.current === null &&
+ // Our internal tests use a custom implementation of `act` that works by
+ // mocking the Scheduler package. Disable the `act` warning.
+ // TODO: Maybe the warning should be disabled by default, and then turned
+ // on at the testing frameworks layer? Instead of what we do now, which
+ // is check if a `jest` global is defined.
+ ReactCurrentActQueue.disableActWarning === false
) {
console.error(
'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
@@ -2849,8 +2851,13 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
if (
warnsIfNotActing === true &&
executionContext === NoContext &&
- IsSomeRendererActing.current === false &&
- IsThisRendererActing.current === false
+ ReactCurrentActQueue.current === null &&
+ // Our internal tests use a custom implementation of `act` that works by
+ // mocking the Scheduler package. Disable the `act` warning.
+ // TODO: Maybe the warning should be disabled by default, and then turned
+ // on at the testing frameworks layer? Instead of what we do now, which
+ // is check if a `jest` global is defined.
+ ReactCurrentActQueue.disableActWarning === false
) {
const previousFiber = ReactCurrentFiberCurrent;
try {
@@ -2880,252 +2887,3 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
}
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
-
-// In tests, we want to enforce a mocked scheduler.
-let didWarnAboutUnmockedScheduler = false;
-// TODO Before we release concurrent mode, revisit this and decide whether a mocked
-// scheduler is the actual recommendation. The alternative could be a testing build,
-// a new lib, or whatever; we dunno just yet. This message is for early adopters
-// to get their tests right.
-
-export function warnIfUnmockedScheduler(fiber: Fiber) {
- if (__DEV__) {
- if (
- didWarnAboutUnmockedScheduler === false &&
- Scheduler.unstable_flushAllWithoutAsserting === undefined
- ) {
- if (fiber.mode & ConcurrentMode) {
- didWarnAboutUnmockedScheduler = true;
- console.error(
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. ' +
- 'For example, with jest: \n' +
- // Break up requires to avoid accidentally parsing them as dependencies.
- "jest.mock('scheduler', () => require" +
- "('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://reactjs.org/link/mock-scheduler',
- );
- } else if (warnAboutUnmockedScheduler === true) {
- didWarnAboutUnmockedScheduler = true;
- console.error(
- 'Starting from React v18, the "scheduler" module will need to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. ' +
- 'For example, with jest: \n' +
- // Break up requires to avoid accidentally parsing them as dependencies.
- "jest.mock('scheduler', () => require" +
- "('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://reactjs.org/link/mock-scheduler',
- );
- }
- }
- }
-}
-
-// `act` testing API
-//
-// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
-// access to the same internals that we do here. Some trade offs in the
-// implementation no longer make sense.
-
-let isFlushingAct = false;
-let isInsideThisAct = false;
-
-function shouldForceFlushFallbacksInDEV() {
- // Never force flush in production. This function should get stripped out.
- return __DEV__ && actingUpdatesScopeDepth > 0;
-}
-
-const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
-const isSchedulerMocked = typeof flushMockScheduler === 'function';
-
-// Returns whether additional work was scheduled. Caller should keep flushing
-// until there's no work left.
-function flushActWork(): boolean {
- if (flushMockScheduler !== undefined) {
- const prevIsFlushing = isFlushingAct;
- isFlushingAct = true;
- try {
- return flushMockScheduler();
- } finally {
- isFlushingAct = prevIsFlushing;
- }
- } else {
- // No mock scheduler available. However, the only type of pending work is
- // passive effects, which we control. So we can flush that.
- const prevIsFlushing = isFlushingAct;
- isFlushingAct = true;
- try {
- let didFlushWork = false;
- while (flushPassiveEffects()) {
- didFlushWork = true;
- }
- return didFlushWork;
- } finally {
- isFlushingAct = prevIsFlushing;
- }
- }
-}
-
-function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
- try {
- flushActWork();
- enqueueTask(() => {
- if (flushActWork()) {
- flushWorkAndMicroTasks(onDone);
- } else {
- onDone();
- }
- });
- } catch (err) {
- onDone(err);
- }
-}
-
-// we track the 'depth' of the act() calls with this counter,
-// so we can tell if any async act() calls try to run in parallel.
-
-let actingUpdatesScopeDepth = 0;
-
-export function act(callback: () => Thenable): Thenable {
- if (!__DEV__) {
- invariant(
- false,
- 'act(...) is not supported in production builds of React.',
- );
- }
-
- const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
- actingUpdatesScopeDepth++;
-
- const previousIsSomeRendererActing = IsSomeRendererActing.current;
- const previousIsThisRendererActing = IsThisRendererActing.current;
- const previousIsInsideThisAct = isInsideThisAct;
- IsSomeRendererActing.current = true;
- IsThisRendererActing.current = true;
- isInsideThisAct = true;
-
- function onDone() {
- actingUpdatesScopeDepth--;
- IsSomeRendererActing.current = previousIsSomeRendererActing;
- IsThisRendererActing.current = previousIsThisRendererActing;
- isInsideThisAct = previousIsInsideThisAct;
- if (__DEV__) {
- if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
- // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
- console.error(
- 'You seem to have overlapping act() calls, this is not supported. ' +
- 'Be sure to await previous act() calls before making a new one. ',
- );
- }
- }
- }
-
- let result;
- try {
- result = batchedUpdates(callback);
- } catch (error) {
- // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
- onDone();
- throw error;
- }
-
- if (
- result !== null &&
- typeof result === 'object' &&
- typeof result.then === 'function'
- ) {
- // setup a boolean that gets set to true only
- // once this act() call is await-ed
- let called = false;
- if (__DEV__) {
- if (typeof Promise !== 'undefined') {
- //eslint-disable-next-line no-undef
- Promise.resolve()
- .then(() => {})
- .then(() => {
- if (called === false) {
- console.error(
- 'You called act(async () => ...) without await. ' +
- 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
- 'calls and mixing their scopes. You should - await act(async () => ...);',
- );
- }
- });
- }
- }
-
- // in the async case, the returned thenable runs the callback, flushes
- // effects and microtasks in a loop until flushPassiveEffects() === false,
- // and cleans up
- return {
- then(resolve, reject) {
- called = true;
- result.then(
- () => {
- if (
- actingUpdatesScopeDepth > 1 ||
- (isSchedulerMocked === true &&
- previousIsSomeRendererActing === true)
- ) {
- onDone();
- resolve();
- return;
- }
- // we're about to exit the act() scope,
- // now's the time to flush tasks/effects
- flushWorkAndMicroTasks((err: ?Error) => {
- onDone();
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- },
- err => {
- onDone();
- reject(err);
- },
- );
- },
- };
- } else {
- if (__DEV__) {
- if (result !== undefined) {
- console.error(
- 'The callback passed to act(...) function ' +
- 'must return undefined, or a Promise. You returned %s',
- result,
- );
- }
- }
-
- // flush effects until none remain, and cleanup
- try {
- if (
- actingUpdatesScopeDepth === 1 &&
- (isSchedulerMocked === false || previousIsSomeRendererActing === false)
- ) {
- // we're about to exit the act() scope,
- // now's the time to flush effects
- flushActWork();
- }
- onDone();
- } catch (err) {
- onDone();
- throw err;
- }
-
- // in the sync case, the returned thenable only warns *if* await-ed
- return {
- then(resolve) {
- if (__DEV__) {
- console.error(
- 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
- );
- }
- resolve();
- },
- };
- }
-}
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index 0b34957078535..d2ccf9e6826af 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -7,7 +7,7 @@
* @flow
*/
-import type {Thenable, Wakeable} from 'shared/ReactTypes';
+import type {Wakeable} from 'shared/ReactTypes';
import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane.old';
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
@@ -24,7 +24,6 @@ import {
enableProfilerCommitHooks,
enableProfilerNestedUpdatePhase,
enableProfilerNestedUpdateScheduledHook,
- warnAboutUnmockedScheduler,
deferRenderPhaseUpdateToNextBatch,
enableDebugTracing,
enableSchedulingProfiler,
@@ -37,8 +36,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
import {
- scheduleCallback,
- cancelCallback,
+ // Aliased because `act` will override and push to an internal queue
+ scheduleCallback as Scheduler_scheduleCallback,
+ cancelCallback as Scheduler_cancelCallback,
shouldYield,
requestPaint,
now,
@@ -79,9 +79,6 @@ import {
markRenderStopped,
} from './SchedulingProfiler';
-// The scheduler is imported here *only* to detect whether it's been mocked
-import * as Scheduler from 'scheduler';
-
import {
resetAfterCommit,
scheduleTimeout,
@@ -238,16 +235,13 @@ import {
} from './ReactFiberDevToolsHook.old';
import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
-// Used by `act`
-import enqueueTask from 'shared/enqueueTask';
-
const ceil = Math.ceil;
const {
ReactCurrentDispatcher,
ReactCurrentOwner,
ReactCurrentBatchConfig,
- IsSomeRendererActing,
+ ReactCurrentActQueue,
} = ReactSharedInternals;
type ExecutionContext = number;
@@ -653,7 +647,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
// Check if there's an existing task. We may be able to reuse it.
const existingCallbackPriority = root.callbackPriority;
- if (existingCallbackPriority === newCallbackPriority) {
+ if (
+ existingCallbackPriority === newCallbackPriority &&
+ // Special case related to `act`. If the currently scheduled task is a
+ // Scheduler task, rather than an `act` task, cancel it and re-scheduled
+ // on the `act` queue.
+ !(
+ __DEV__ &&
+ ReactCurrentActQueue.current !== null &&
+ existingCallbackNode !== fakeActCallbackNode
+ )
+ ) {
if (__DEV__) {
// If we're going to re-use an existing task, it needs to exist.
// Assume that discrete update microtasks are non-cancellable and null.
@@ -2781,42 +2785,35 @@ export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void {
}
}
-export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
+const fakeActCallbackNode = {};
+function scheduleCallback(priorityLevel, callback) {
if (__DEV__) {
- if (
- warnsIfNotActing === true &&
- IsSomeRendererActing.current === true &&
- IsThisRendererActing.current !== true
- ) {
- const previousFiber = ReactCurrentFiberCurrent;
- try {
- setCurrentDebugFiberInDEV(fiber);
- console.error(
- "It looks like you're using the wrong act() around your test interactions.\n" +
- 'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
- '// for react-dom:\n' +
- // Break up imports to avoid accidentally parsing them as dependencies.
- 'import {act} fr' +
- "om 'react-dom/test-utils';\n" +
- '// ...\n' +
- 'act(() => ...);\n\n' +
- '// for react-test-renderer:\n' +
- // Break up imports to avoid accidentally parsing them as dependencies.
- 'import TestRenderer fr' +
- "om 'react-test-renderer';\n" +
- 'const {act} = TestRenderer;\n' +
- '// ...\n' +
- 'act(() => ...);',
- );
- } finally {
- if (previousFiber) {
- setCurrentDebugFiberInDEV(fiber);
- } else {
- resetCurrentDebugFiberInDEV();
- }
- }
+ // If we're currently inside an `act` scope, bypass Scheduler and push to
+ // the `act` queue instead.
+ const actQueue = ReactCurrentActQueue.current;
+ if (actQueue !== null) {
+ actQueue.push(callback);
+ return fakeActCallbackNode;
+ } else {
+ return Scheduler_scheduleCallback(priorityLevel, callback);
}
+ } else {
+ // In production, always call Scheduler. This function will be stripped out.
+ return Scheduler_scheduleCallback(priorityLevel, callback);
+ }
+}
+
+function cancelCallback(callbackNode) {
+ if (__DEV__ && callbackNode === fakeActCallbackNode) {
+ return;
}
+ // In production, always call Scheduler. This function will be stripped out.
+ return Scheduler_cancelCallback(callbackNode);
+}
+
+function shouldForceFlushFallbacksInDEV() {
+ // Never force flush in production. This function should get stripped out.
+ return __DEV__ && ReactCurrentActQueue.current !== null;
}
export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
@@ -2824,8 +2821,13 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
if (
warnsIfNotActing === true &&
(fiber.mode & StrictLegacyMode) !== NoMode &&
- IsSomeRendererActing.current === false &&
- IsThisRendererActing.current === false
+ ReactCurrentActQueue.current === null &&
+ // Our internal tests use a custom implementation of `act` that works by
+ // mocking the Scheduler package. Disable the `act` warning.
+ // TODO: Maybe the warning should be disabled by default, and then turned
+ // on at the testing frameworks layer? Instead of what we do now, which
+ // is check if a `jest` global is defined.
+ ReactCurrentActQueue.disableActWarning === false
) {
console.error(
'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
@@ -2849,8 +2851,13 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
if (
warnsIfNotActing === true &&
executionContext === NoContext &&
- IsSomeRendererActing.current === false &&
- IsThisRendererActing.current === false
+ ReactCurrentActQueue.current === null &&
+ // Our internal tests use a custom implementation of `act` that works by
+ // mocking the Scheduler package. Disable the `act` warning.
+ // TODO: Maybe the warning should be disabled by default, and then turned
+ // on at the testing frameworks layer? Instead of what we do now, which
+ // is check if a `jest` global is defined.
+ ReactCurrentActQueue.disableActWarning === false
) {
const previousFiber = ReactCurrentFiberCurrent;
try {
@@ -2880,252 +2887,3 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
}
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
-
-// In tests, we want to enforce a mocked scheduler.
-let didWarnAboutUnmockedScheduler = false;
-// TODO Before we release concurrent mode, revisit this and decide whether a mocked
-// scheduler is the actual recommendation. The alternative could be a testing build,
-// a new lib, or whatever; we dunno just yet. This message is for early adopters
-// to get their tests right.
-
-export function warnIfUnmockedScheduler(fiber: Fiber) {
- if (__DEV__) {
- if (
- didWarnAboutUnmockedScheduler === false &&
- Scheduler.unstable_flushAllWithoutAsserting === undefined
- ) {
- if (fiber.mode & ConcurrentMode) {
- didWarnAboutUnmockedScheduler = true;
- console.error(
- 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. ' +
- 'For example, with jest: \n' +
- // Break up requires to avoid accidentally parsing them as dependencies.
- "jest.mock('scheduler', () => require" +
- "('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://reactjs.org/link/mock-scheduler',
- );
- } else if (warnAboutUnmockedScheduler === true) {
- didWarnAboutUnmockedScheduler = true;
- console.error(
- 'Starting from React v18, the "scheduler" module will need to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. ' +
- 'For example, with jest: \n' +
- // Break up requires to avoid accidentally parsing them as dependencies.
- "jest.mock('scheduler', () => require" +
- "('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://reactjs.org/link/mock-scheduler',
- );
- }
- }
- }
-}
-
-// `act` testing API
-//
-// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
-// access to the same internals that we do here. Some trade offs in the
-// implementation no longer make sense.
-
-let isFlushingAct = false;
-let isInsideThisAct = false;
-
-function shouldForceFlushFallbacksInDEV() {
- // Never force flush in production. This function should get stripped out.
- return __DEV__ && actingUpdatesScopeDepth > 0;
-}
-
-const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
-const isSchedulerMocked = typeof flushMockScheduler === 'function';
-
-// Returns whether additional work was scheduled. Caller should keep flushing
-// until there's no work left.
-function flushActWork(): boolean {
- if (flushMockScheduler !== undefined) {
- const prevIsFlushing = isFlushingAct;
- isFlushingAct = true;
- try {
- return flushMockScheduler();
- } finally {
- isFlushingAct = prevIsFlushing;
- }
- } else {
- // No mock scheduler available. However, the only type of pending work is
- // passive effects, which we control. So we can flush that.
- const prevIsFlushing = isFlushingAct;
- isFlushingAct = true;
- try {
- let didFlushWork = false;
- while (flushPassiveEffects()) {
- didFlushWork = true;
- }
- return didFlushWork;
- } finally {
- isFlushingAct = prevIsFlushing;
- }
- }
-}
-
-function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
- try {
- flushActWork();
- enqueueTask(() => {
- if (flushActWork()) {
- flushWorkAndMicroTasks(onDone);
- } else {
- onDone();
- }
- });
- } catch (err) {
- onDone(err);
- }
-}
-
-// we track the 'depth' of the act() calls with this counter,
-// so we can tell if any async act() calls try to run in parallel.
-
-let actingUpdatesScopeDepth = 0;
-
-export function act(callback: () => Thenable): Thenable {
- if (!__DEV__) {
- invariant(
- false,
- 'act(...) is not supported in production builds of React.',
- );
- }
-
- const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
- actingUpdatesScopeDepth++;
-
- const previousIsSomeRendererActing = IsSomeRendererActing.current;
- const previousIsThisRendererActing = IsThisRendererActing.current;
- const previousIsInsideThisAct = isInsideThisAct;
- IsSomeRendererActing.current = true;
- IsThisRendererActing.current = true;
- isInsideThisAct = true;
-
- function onDone() {
- actingUpdatesScopeDepth--;
- IsSomeRendererActing.current = previousIsSomeRendererActing;
- IsThisRendererActing.current = previousIsThisRendererActing;
- isInsideThisAct = previousIsInsideThisAct;
- if (__DEV__) {
- if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
- // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
- console.error(
- 'You seem to have overlapping act() calls, this is not supported. ' +
- 'Be sure to await previous act() calls before making a new one. ',
- );
- }
- }
- }
-
- let result;
- try {
- result = batchedUpdates(callback);
- } catch (error) {
- // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
- onDone();
- throw error;
- }
-
- if (
- result !== null &&
- typeof result === 'object' &&
- typeof result.then === 'function'
- ) {
- // setup a boolean that gets set to true only
- // once this act() call is await-ed
- let called = false;
- if (__DEV__) {
- if (typeof Promise !== 'undefined') {
- //eslint-disable-next-line no-undef
- Promise.resolve()
- .then(() => {})
- .then(() => {
- if (called === false) {
- console.error(
- 'You called act(async () => ...) without await. ' +
- 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
- 'calls and mixing their scopes. You should - await act(async () => ...);',
- );
- }
- });
- }
- }
-
- // in the async case, the returned thenable runs the callback, flushes
- // effects and microtasks in a loop until flushPassiveEffects() === false,
- // and cleans up
- return {
- then(resolve, reject) {
- called = true;
- result.then(
- () => {
- if (
- actingUpdatesScopeDepth > 1 ||
- (isSchedulerMocked === true &&
- previousIsSomeRendererActing === true)
- ) {
- onDone();
- resolve();
- return;
- }
- // we're about to exit the act() scope,
- // now's the time to flush tasks/effects
- flushWorkAndMicroTasks((err: ?Error) => {
- onDone();
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- },
- err => {
- onDone();
- reject(err);
- },
- );
- },
- };
- } else {
- if (__DEV__) {
- if (result !== undefined) {
- console.error(
- 'The callback passed to act(...) function ' +
- 'must return undefined, or a Promise. You returned %s',
- result,
- );
- }
- }
-
- // flush effects until none remain, and cleanup
- try {
- if (
- actingUpdatesScopeDepth === 1 &&
- (isSchedulerMocked === false || previousIsSomeRendererActing === false)
- ) {
- // we're about to exit the act() scope,
- // now's the time to flush effects
- flushActWork();
- }
- onDone();
- } catch (err) {
- onDone();
- throw err;
- }
-
- // in the sync case, the returned thenable only warns *if* await-ed
- return {
- then(resolve) {
- if (__DEV__) {
- console.error(
- 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
- );
- }
- resolve();
- },
- };
- }
-}
diff --git a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
index 8348c708f3bc5..35c22e672ed3e 100644
--- a/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
@@ -11,6 +11,7 @@
'use strict';
let React;
+let act;
let ReactFiberReconciler;
let ConcurrentRoot;
let DefaultEventPriority;
@@ -19,6 +20,7 @@ describe('ReactFiberHostContext', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
+ act = React.unstable_act;
ReactFiberReconciler = require('react-reconciler');
ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
.ConcurrentRoot;
@@ -71,7 +73,7 @@ describe('ReactFiberHostContext', () => {
false,
null,
);
- Renderer.act(() => {
+ act(() => {
Renderer.updateContainer(
@@ -132,7 +134,7 @@ describe('ReactFiberHostContext', () => {
false,
null,
);
- Renderer.act(() => {
+ act(() => {
Renderer.updateContainer(
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index 246790ead3a46..469807090ec62 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -12,6 +12,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {Instance, TextInstance} from './ReactTestHostConfig';
+import * as React from 'react';
import * as Scheduler from 'scheduler/unstable_mock';
import {
getPublicRootInstance,
@@ -20,7 +21,6 @@ import {
flushSync,
injectIntoDevTools,
batchedUpdates,
- act,
IsThisRendererActing,
} from 'react-reconciler/src/ReactFiberReconciler';
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
@@ -53,7 +53,14 @@ import {getPublicInstance} from './ReactTestHostConfig';
import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
-const {IsSomeRendererActing} = ReactSharedInternals;
+const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
+
+const act_notBatchedInLegacyMode = React.unstable_act;
+function act(callback: () => Thenable): Thenable {
+ return act_notBatchedInLegacyMode(() => {
+ return batchedUpdates(callback);
+ });
+}
type TestRendererOptions = {
createNodeMock: (element: React$Element) => any,
@@ -604,6 +611,8 @@ let actingUpdatesScopeDepth = 0;
// building an app with React.
// TODO: Migrate our tests to use ReactNoop. Although we would need to figure
// out a solution for Relay, which has some Concurrent Mode tests.
+// TODO: Replace the internal "concurrent" implementations of `act` with a
+// single shared module.
function unstable_concurrentAct(scope: () => Thenable | void) {
if (Scheduler.unstable_flushAllWithoutAsserting === undefined) {
throw Error(
@@ -623,8 +632,14 @@ function unstable_concurrentAct(scope: () => Thenable | void) {
IsSomeRendererActing.current = true;
IsThisRendererActing.current = true;
actingUpdatesScopeDepth++;
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = true;
+ }
const unwind = () => {
+ if (__DEV__ && actingUpdatesScopeDepth === 1) {
+ ReactCurrentActQueue.disableActWarning = false;
+ }
actingUpdatesScopeDepth--;
IsSomeRendererActing.current = previousIsSomeRendererActing;
IsThisRendererActing.current = previousIsThisRendererActing;
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
index 28cc0062e8a7b..4fe1afea6d62f 100644
--- a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
@@ -40,23 +40,6 @@ describe('ReactTestRenderer.act()', () => {
expect(root.toJSON()).toEqual('1');
});
- it("warns if you don't use .act", () => {
- let setCtr;
- function App(props) {
- const [ctr, _setCtr] = React.useState(0);
- setCtr = _setCtr;
- return ctr;
- }
-
- ReactTestRenderer.create();
-
- expect(() => {
- setCtr(1);
- }).toErrorDev([
- 'An update to App inside a test was not wrapped in act(...)',
- ]);
- });
-
describe('async', () => {
// @gate __DEV__
it('should work with async/await', async () => {
diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js
index 15f3447a111f8..653013c7b0797 100644
--- a/packages/react/index.classic.fb.js
+++ b/packages/react/index.classic.fb.js
@@ -9,6 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ act as unstable_act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js
index 5c7197b282cdc..ab90ea66bc112 100644
--- a/packages/react/index.experimental.js
+++ b/packages/react/index.experimental.js
@@ -9,6 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ act as unstable_act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.js b/packages/react/index.js
index 59e76eff92fa3..247d17ec01b8d 100644
--- a/packages/react/index.js
+++ b/packages/react/index.js
@@ -33,6 +33,7 @@ export type ChildrenArray<+T> = $ReadOnlyArray> | T;
// We can't use export * from in Flow for some reason.
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ act as unstable_act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js
index 47b629832b332..4f316eacad8b7 100644
--- a/packages/react/index.modern.fb.js
+++ b/packages/react/index.modern.fb.js
@@ -9,6 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ act as unstable_act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js
index 33e1b37693bd1..008875e4577e5 100644
--- a/packages/react/index.stable.js
+++ b/packages/react/index.stable.js
@@ -9,6 +9,7 @@
export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
+ act as unstable_act,
Children,
Component,
Fragment,
diff --git a/packages/react/src/React.js b/packages/react/src/React.js
index a99d2a331b20a..2b87d18b6c81d 100644
--- a/packages/react/src/React.js
+++ b/packages/react/src/React.js
@@ -60,6 +60,7 @@ import {
import {createMutableSource} from './ReactMutableSource';
import ReactSharedInternals from './ReactSharedInternals';
import {startTransition} from './ReactStartTransition';
+import {act} from './ReactAct';
// TODO: Move this branching into the other module instead and just re-export.
const createElement = __DEV__ ? createElementWithValidation : createElementProd;
@@ -120,4 +121,5 @@ export {
// enableScopeAPI
REACT_SCOPE_TYPE as unstable_Scope,
useOpaqueIdentifier as unstable_useOpaqueIdentifier,
+ act,
};
diff --git a/packages/react/src/ReactAct.js b/packages/react/src/ReactAct.js
new file mode 100644
index 0000000000000..885e803b027c5
--- /dev/null
+++ b/packages/react/src/ReactAct.js
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+import type {Thenable} from 'shared/ReactTypes';
+import ReactCurrentActQueue from './ReactCurrentActQueue';
+import invariant from 'shared/invariant';
+import enqueueTask from 'shared/enqueueTask';
+
+let actScopeDepth = 0;
+let didWarnNoAwaitAct = false;
+
+export function act(callback: () => Thenable): Thenable {
+ if (__DEV__) {
+ // `act` calls can be nested, so we track the depth. This represents the
+ // number of `act` scopes on the stack.
+ const prevActScopeDepth = actScopeDepth;
+ actScopeDepth++;
+
+ if (ReactCurrentActQueue.current === null) {
+ // This is the outermost `act` scope. Initialize the queue. The reconciler
+ // will detect the queue and use it instead of Scheduler.
+ ReactCurrentActQueue.current = [];
+ }
+
+ let result;
+ try {
+ result = callback();
+ } catch (error) {
+ popActScope(prevActScopeDepth);
+ throw error;
+ }
+
+ if (
+ result !== null &&
+ typeof result === 'object' &&
+ typeof result.then === 'function'
+ ) {
+ // The callback is an async function (i.e. returned a promise). Wait
+ // for it to resolve before exiting the current scope.
+ let wasAwaited = false;
+ const thenable = {
+ then(resolve, reject) {
+ wasAwaited = true;
+ result.then(
+ () => {
+ popActScope(prevActScopeDepth);
+ if (actScopeDepth === 0) {
+ // We've exited the outermost act scope. Recursively flush the
+ // queue until there's no remaining work.
+ recursivelyFlushAsyncActWork(resolve, reject);
+ } else {
+ resolve();
+ }
+ },
+ error => {
+ // The callback threw an error.
+ popActScope(prevActScopeDepth);
+ reject(error);
+ },
+ );
+ },
+ };
+
+ if (__DEV__) {
+ if (!didWarnNoAwaitAct && typeof Promise !== 'undefined') {
+ // eslint-disable-next-line no-undef
+ Promise.resolve()
+ .then(() => {})
+ .then(() => {
+ if (!wasAwaited) {
+ didWarnNoAwaitAct = true;
+ console.error(
+ 'You called act(async () => ...) without await. ' +
+ 'This could lead to unexpected testing behaviour, ' +
+ 'interleaving multiple act calls and mixing their ' +
+ 'scopes. ' +
+ 'You should - await act(async () => ...);',
+ );
+ }
+ });
+ }
+ }
+ return thenable;
+ } else {
+ // The callback is not an async function. Exit the current scope
+ // immediately, without awaiting.
+ popActScope(prevActScopeDepth);
+ if (actScopeDepth === 0) {
+ // Exiting the outermost act scope. Flush the queue.
+ const queue = ReactCurrentActQueue.current;
+ if (queue !== null) {
+ flushActQueue(queue);
+ ReactCurrentActQueue.current = null;
+ }
+ // Return a thenable. If the user awaits it, we'll flush again in
+ // case additional work was scheduled by a microtask.
+ return {
+ then(resolve, reject) {
+ // Confirm we haven't re-entered another `act` scope, in case
+ // the user does something weird like await the thenable
+ // multiple times.
+ if (ReactCurrentActQueue.current === null) {
+ // Recursively flush the queue until there's no remaining work.
+ ReactCurrentActQueue.current = [];
+ recursivelyFlushAsyncActWork(resolve, reject);
+ }
+ },
+ };
+ } else {
+ // Since we're inside a nested `act` scope, the returned thenable
+ // immediately resolves. The outer scope will flush the queue.
+ return {
+ then(resolve, reject) {
+ resolve();
+ },
+ };
+ }
+ }
+ } else {
+ invariant(
+ false,
+ 'act(...) is not supported in production builds of React.',
+ );
+ }
+}
+
+function popActScope(prevActScopeDepth) {
+ if (__DEV__) {
+ if (prevActScopeDepth !== actScopeDepth - 1) {
+ console.error(
+ 'You seem to have overlapping act() calls, this is not supported. ' +
+ 'Be sure to await previous act() calls before making a new one. ',
+ );
+ }
+ actScopeDepth = prevActScopeDepth;
+ }
+}
+
+function recursivelyFlushAsyncActWork(resolve, reject) {
+ if (__DEV__) {
+ const queue = ReactCurrentActQueue.current;
+ if (queue !== null) {
+ try {
+ flushActQueue(queue);
+ enqueueTask(() => {
+ if (queue.length === 0) {
+ // No additional work was scheduled. Finish.
+ ReactCurrentActQueue.current = null;
+ resolve();
+ } else {
+ // Keep flushing work until there's none left.
+ recursivelyFlushAsyncActWork(resolve, reject);
+ }
+ });
+ } catch (error) {
+ reject(error);
+ }
+ } else {
+ resolve();
+ }
+ }
+}
+
+let isFlushing = false;
+function flushActQueue(queue) {
+ if (__DEV__) {
+ if (!isFlushing) {
+ // Prevent re-entrancy.
+ isFlushing = true;
+ let i = 0;
+ try {
+ for (; i < queue.length; i++) {
+ let callback = queue[i];
+ do {
+ callback = callback(true);
+ } while (callback !== null);
+ }
+ queue.length = 0;
+ } catch (error) {
+ // If something throws, leave the remaining callbacks on the queue.
+ queue = queue.slice(i + 1);
+ throw error;
+ } finally {
+ isFlushing = false;
+ }
+ }
+ }
+}
diff --git a/packages/react/src/ReactCurrentActQueue.js b/packages/react/src/ReactCurrentActQueue.js
new file mode 100644
index 0000000000000..1b7966337cad5
--- /dev/null
+++ b/packages/react/src/ReactCurrentActQueue.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow
+ */
+
+type RendererTask = boolean => RendererTask | null;
+
+const ReactCurrentActQueue = {
+ current: (null: null | Array),
+ // Our internal tests use a custom implementation of `act` that works by
+ // mocking the Scheduler package. Use this field to disable the `act` warning.
+ // TODO: Maybe the warning should be disabled by default, and then turned
+ // on at the testing frameworks layer? Instead of what we do now, which
+ // is check if a `jest` global is defined.
+ disableActWarning: (false: boolean),
+};
+
+export default ReactCurrentActQueue;
diff --git a/packages/react/src/ReactSharedInternals.js b/packages/react/src/ReactSharedInternals.js
index 791d41adf8044..e95ea15848751 100644
--- a/packages/react/src/ReactSharedInternals.js
+++ b/packages/react/src/ReactSharedInternals.js
@@ -8,6 +8,7 @@
import assign from 'object-assign';
import ReactCurrentDispatcher from './ReactCurrentDispatcher';
import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
+import ReactCurrentActQueue from './ReactCurrentActQueue';
import ReactCurrentOwner from './ReactCurrentOwner';
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
import IsSomeRendererActing from './IsSomeRendererActing';
@@ -23,6 +24,7 @@ const ReactSharedInternals = {
if (__DEV__) {
ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
+ ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue;
}
export default ReactSharedInternals;
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 60efb97e42017..d966c9ec54c9d 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -78,7 +78,6 @@ export const enableCreateEventHandleAPI = false;
// We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v18?)
// Till then, we warn about the missing mock, but still fallback to a legacy mode compatible version
-export const warnAboutUnmockedScheduler = false;
// Add a callback property to suspense to notify which promises are currently
// in the update queue. This allows reporting and tracing of what is causing
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index a58e9e543895f..76cfa7d18f3ea 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -30,7 +30,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
export const warnAboutDeprecatedLifecycles = true;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = true;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index 1fc0969a3fa90..f8595c01b9274 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index fe3abe658bdd4..528f42ddc3cc0 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
index bdf3607a1ee82..6a70e11a4c557 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index a4f209a6b9f30..ef095eef7e7bd 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -29,7 +29,6 @@ export const disableJavaScriptURLs = false;
export const disableInputAttributeSyncing = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js
index 5c8baa8a12049..58c6400b0aeef 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.js
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
-export const warnAboutUnmockedScheduler = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js
index 30cf572ddce21..0c3d15890b277 100644
--- a/packages/shared/forks/ReactFeatureFlags.testing.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
export const enableSchedulerDebugging = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = true;
-export const warnAboutUnmockedScheduler = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutStringRefs = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 04abc20de6464..e028c9ddb8ef9 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -75,8 +75,6 @@ export const enableCreateEventHandleAPI = true;
export const enableScopeAPI = true;
-export const warnAboutUnmockedScheduler = true;
-
export const enableSuspenseCallback = true;
export const enableComponentStackLocations = true;
diff --git a/scripts/jest/setupTests.www.js b/scripts/jest/setupTests.www.js
index 3812d8f1354b5..e303d9fee22a5 100644
--- a/scripts/jest/setupTests.www.js
+++ b/scripts/jest/setupTests.www.js
@@ -14,7 +14,6 @@ jest.mock('shared/ReactFeatureFlags', () => {
// www configuration. Update those tests so that they work against the www
// configuration, too. Then remove these overrides.
wwwFlags.disableLegacyContext = defaultFlags.disableLegacyContext;
- wwwFlags.warnAboutUnmockedScheduler = defaultFlags.warnAboutUnmockedScheduler;
wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs;
return wwwFlags;