@samitouri / QOS-React / commits / 06f7b4f43a

`act` should work without mock Scheduler (#21714)

Currently, in a React 18 root, `act` only works if you mock the Scheduler package. This was because we didn't want to add additional checks at runtime. But now that the `act` testing API is dev-only, we can simplify its implementation. Now when an update is wrapped with `act`, React will bypass Scheduler entirely and push its tasks onto a special internal queue. Then, when the outermost `act` scope exists, we'll flush that queue. I also removed the "wrong act" warning, because the plan is to move `act` to an isomorphic entry point, simlar to `startTransition`. That's not directly related to this PR, but I didn't want to bother re-implementing that warning only to immediately remove it. I'll add the isomorphic API in a follow up. Note that the internal version of `act` that we use in our own tests still depends on mocking the Scheduler package, because it needs to work in production. I'm planning to move that implementation to a shared (internal) module, too.

Andrew Clark committed Jun 22, 2021 at 17:25 UTC 06f7b4f43a93eae9111cfbd972ab1ae1148f6afb
44 files changed +423 -1004
fixtures/dom/src/__tests__/nested-act-test.js
+1 -1
@@ -48,7 +48,7 @@ describe('unmocked scheduler', () => {
48 TestAct(() => {
49 TestRenderer.create(<Effecty />);
50 });
51 - expect(log).toEqual(['called']);
51 + expect(log).toEqual([]);
52 });
53 expect(log).toEqual(['called']);
54 });
fixtures/dom/src/__tests__/wrong-act-test.js deleted
-207
@@ -1,207 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -let React;
11 -let ReactDOM;
12 -let ReactART;
13 -let TestUtils;
14 -let ARTSVGMode;
15 -let ARTCurrentMode;
16 -let TestRenderer;
17 -let ARTTest;
18 -
19 -global.__DEV__ = process.env.NODE_ENV !== 'production';
20 -global.__EXPERIMENTAL__ = process.env.RELEASE_CHANNEL === 'experimental';
21 -
22 -expect.extend(require('../toWarnDev'));
23 -
24 -function App(props) {
25 - return 'hello world';
26 -}
27 -
28 -beforeEach(() => {
29 - jest.resetModules();
30 - React = require('react');
31 - ReactDOM = require('react-dom');
32 - TestUtils = require('react-dom/test-utils');
33 - ReactART = require('react-art');
34 - ARTSVGMode = require('art/modes/svg');
35 - ARTCurrentMode = require('art/modes/current');
36 - TestRenderer = require('react-test-renderer');
37 -
38 - ARTCurrentMode.setCurrent(ARTSVGMode);
39 -
40 - ARTTest = function ARTTestComponent(props) {
41 - return (
42 - <ReactART.Surface width={150} height={200}>
43 - <ReactART.Group>
44 - <ReactART.Shape
45 - d="M0,0l50,0l0,50l-50,0z"
46 - fill={new ReactART.LinearGradient(['black', 'white'])}
47 - key="a"
48 - width={50}
49 - height={50}
50 - x={50}
51 - y={50}
52 - opacity={0.1}
53 - />
54 - <ReactART.Shape
55 - fill="#3C5A99"
56 - key="b"
57 - scale={0.5}
58 - x={50}
59 - y={50}
60 - title="This is an F"
61 - cursor="pointer">
62 - M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
63 - h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
64 - h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
65 - </ReactART.Shape>
66 - </ReactART.Group>
67 - </ReactART.Surface>
68 - );
69 - };
70 -});
71 -
72 -it("doesn't warn when you use the right act + renderer: dom", () => {
73 - TestUtils.act(() => {
74 - ReactDOM.render(<App />, document.createElement('div'));
75 - });
76 -});
77 -
78 -it("doesn't warn when you use the right act + renderer: test", () => {
79 - TestRenderer.act(() => {
80 - TestRenderer.create(<App />);
81 - });
82 -});
83 -
84 -it('resets correctly across renderers', async () => {
85 - function Effecty() {
86 - React.useEffect(() => {}, []);
87 - return null;
88 - }
89 - await TestUtils.act(async () => {
90 - TestRenderer.act(() => {});
91 - expect(() => {
92 - TestRenderer.create(<Effecty />);
93 - }).toWarnDev(["It looks like you're using the wrong act()"], {
94 - withoutStack: true,
95 - });
96 - });
97 -});
98 -
99 -it('warns when using the wrong act version - test + dom: render', () => {
100 - expect(() => {
101 - TestRenderer.act(() => {
102 - ReactDOM.render(<App />, document.createElement('div'));
103 - });
104 - }).toWarnDev(
105 - [
106 - 'ReactDOM.render is no longer supported in React 18.',
107 - "It looks like you're using the wrong act()",
108 - ],
109 - {
110 - withoutStack: true,
111 - }
112 - );
113 -});
114 -
115 -it('warns when using the wrong act version - test + dom: updates', () => {
116 - let setCtr;
117 - function Counter(props) {
118 - const [ctr, _setCtr] = React.useState(0);
119 - setCtr = _setCtr;
120 - return ctr;
121 - }
122 - ReactDOM.render(<Counter />, document.createElement('div'));
123 - expect(() => {
124 - TestRenderer.act(() => {
125 - setCtr(1);
126 - });
127 - }).toWarnDev(["It looks like you're using the wrong act()"], {
128 - withoutStack: true,
129 - });
130 -});
131 -
132 -it('warns when using the wrong act version - dom + test: .create()', () => {
133 - expect(() => {
134 - TestUtils.act(() => {
135 - TestRenderer.create(<App />);
136 - });
137 - }).toWarnDev(["It looks like you're using the wrong act()"], {
138 - withoutStack: true,
139 - });
140 -});
141 -
142 -it('warns when using the wrong act version - dom + test: .update()', () => {
143 - const root = TestRenderer.create(<App key="one" />);
144 - expect(() => {
145 - TestUtils.act(() => {
146 - root.update(<App key="two" />);
147 - });
148 - }).toWarnDev(["It looks like you're using the wrong act()"], {
149 - withoutStack: true,
150 - });
151 -});
152 -
153 -it('warns when using the wrong act version - dom + test: updates', () => {
154 - let setCtr;
155 - function Counter(props) {
156 - const [ctr, _setCtr] = React.useState(0);
157 - setCtr = _setCtr;
158 - return ctr;
159 - }
160 - TestRenderer.create(<Counter />);
161 - expect(() => {
162 - TestUtils.act(() => {
163 - setCtr(1);
164 - });
165 - }).toWarnDev(["It looks like you're using the wrong act()"], {
166 - withoutStack: true,
167 - });
168 -});
169 -
170 -it('does not warn when nesting react-act inside react-dom', () => {
171 - TestUtils.act(() => {
172 - ReactDOM.render(<ARTTest />, document.createElement('div'));
173 - });
174 -});
175 -
176 -it('does not warn when nesting react-act inside react-test-renderer', () => {
177 - TestRenderer.act(() => {
178 - TestRenderer.create(<ARTTest />);
179 - });
180 -});
181 -
182 -it("doesn't warn if you use nested acts from different renderers", () => {
183 - TestRenderer.act(() => {
184 - TestUtils.act(() => {
185 - TestRenderer.create(<App />);
186 - });
187 - });
188 -});
189 -
190 -if (__EXPERIMENTAL__) {
191 - it('warns when using createRoot() + .render', () => {
192 - const root = ReactDOM.createRoot(document.createElement('div'));
193 - expect(() => {
194 - TestRenderer.act(() => {
195 - root.render(<App />);
196 - });
197 - }).toWarnDev(
198 - [
199 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked',
200 - "It looks like you're using the wrong act()",
201 - ],
202 - {
203 - withoutStack: true,
204 - }
205 - );
206 - });
207 -}
packages/react-dom/src/__tests__/ReactDOMTestSelectors-test.internal.js
+1 -1
@@ -33,7 +33,7 @@ describe('ReactDOMTestSelectors', () => {
33 React = require('react');
34
35 const ReactDOM = require('react-dom/testing');
36 - act = ReactDOM.act;
36 + act = React.unstable_act;
37 createComponentSelector = ReactDOM.createComponentSelector;
38 createHasPseudoClassSelector = ReactDOM.createHasPseudoClassSelector;
39 createRoleSelector = ReactDOM.createRoleSelector;
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+37 -30
@@ -354,32 +354,6 @@ function runActTests(label, render, unmount, rerender) {
354 expect(container.innerHTML).toBe('2');
355 });
356 });
357 -
358 - // @gate __DEV__
359 - it('warns if you return a value inside act', () => {
360 - expect(() => act(() => null)).toErrorDev(
361 - [
362 - 'The callback passed to act(...) function must return undefined, or a Promise.',
363 - ],
364 - {withoutStack: true},
365 - );
366 - expect(() => act(() => 123)).toErrorDev(
367 - [
368 - 'The callback passed to act(...) function must return undefined, or a Promise.',
369 - ],
370 - {withoutStack: true},
371 - );
372 - });
373 -
374 - // @gate __DEV__
375 - it('warns if you try to await a sync .act call', () => {
376 - expect(() => act(() => {}).then(() => {})).toErrorDev(
377 - [
378 - 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
379 - ],
380 - {withoutStack: true},
381 - );
382 - });
357 });
358
359 describe('asynchronous tests', () => {
@@ -401,15 +375,17 @@ function runActTests(label, render, unmount, rerender) {
375
376 await act(async () => {
377 render(<App />, container);
404 - // flush a little to start the timer
405 - expect(Scheduler).toFlushAndYield([]);
378 + });
379 + expect(container.innerHTML).toBe('0');
380 + // Flush the pending timers
381 + await act(async () => {
382 await sleep(100);
383 });
384 expect(container.innerHTML).toBe('1');
385 });
386
387 // @gate __DEV__
412 - it('flushes microtasks before exiting', async () => {
388 + it('flushes microtasks before exiting (async function)', async () => {
389 function App() {
390 const [ctr, setCtr] = React.useState(0);
391 async function someAsyncFunction() {
@@ -431,6 +407,31 @@ function runActTests(label, render, unmount, rerender) {
407 expect(container.innerHTML).toEqual('1');
408 });
409
410 + // @gate __DEV__
411 + it('flushes microtasks before exiting (sync function)', async () => {
412 + // Same as previous test, but the callback passed to `act` is not itself
413 + // an async function.
414 + function App() {
415 + const [ctr, setCtr] = React.useState(0);
416 + async function someAsyncFunction() {
417 + // queue a bunch of promises to be sure they all flush
418 + await null;
419 + await null;
420 + await null;
421 + setCtr(1);
422 + }
423 + React.useEffect(() => {
424 + someAsyncFunction();
425 + }, []);
426 + return ctr;
427 + }
428 +
429 + await act(() => {
430 + render(<App />, container);
431 + });
432 + expect(container.innerHTML).toEqual('1');
433 + });
434 +
435 // @gate __DEV__
436 it('warns if you do not await an act call', async () => {
437 spyOnDevAndProd(console, 'error');
@@ -461,7 +462,13 @@ function runActTests(label, render, unmount, rerender) {
462
463 await sleep(150);
464 if (__DEV__) {
464 - expect(console.error).toHaveBeenCalledTimes(1);
465 + expect(console.error).toHaveBeenCalledTimes(2);
466 + expect(console.error.calls.argsFor(0)[0]).toMatch(
467 + 'You seem to have overlapping act() calls',
468 + );
469 + expect(console.error.calls.argsFor(1)[0]).toMatch(
470 + 'You seem to have overlapping act() calls',
471 + );
472 }
473 });
474
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js deleted
-56
@@ -1,56 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -let React;
11 -let ReactDOM;
12 -let ReactFeatureFlags;
13 -
14 -function App() {
15 - return null;
16 -}
17 -
18 -beforeEach(() => {
19 - jest.resetModules();
20 - jest.unmock('scheduler');
21 - React = require('react');
22 - ReactDOM = require('react-dom');
23 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
24 - ReactFeatureFlags.warnAboutUnmockedScheduler = true;
25 -});
26 -
27 -afterEach(() => {
28 - ReactFeatureFlags.warnAboutUnmockedScheduler = false;
29 -});
30 -
31 -it('should warn in legacy mode', () => {
32 - expect(() => {
33 - ReactDOM.render(<App />, document.createElement('div'));
34 - }).toErrorDev(
35 - ['Starting from React v18, the "scheduler" module will need to be mocked'],
36 - {withoutStack: true},
37 - );
38 - // does not warn twice
39 - expect(() => {
40 - ReactDOM.render(<App />, document.createElement('div'));
41 - }).toErrorDev([]);
42 -});
43 -
44 -it('does not warn if Scheduler is mocked', () => {
45 - jest.resetModules();
46 - jest.mock('scheduler', () => require('scheduler/unstable_mock'));
47 - React = require('react');
48 - ReactDOM = require('react-dom');
49 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
50 - ReactFeatureFlags.warnAboutUnmockedScheduler = true;
51 -
52 - // This should not warn
53 - expect(() => {
54 - ReactDOM.render(<App />, document.createElement('div'));
55 - }).toErrorDev([]);
56 -});
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js deleted
-42
@@ -1,42 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -let React;
11 -let ReactDOM;
12 -
13 -function App() {
14 - return null;
15 -}
16 -
17 -beforeEach(() => {
18 - jest.resetModules();
19 - jest.unmock('scheduler');
20 - React = require('react');
21 - ReactDOM = require('react-dom');
22 -});
23 -
24 -it('does not warn when rendering in legacy mode', () => {
25 - expect(() => {
26 - ReactDOM.render(<App />, document.createElement('div'));
27 - }).toErrorDev([]);
28 -});
29 -
30 -it('should warn when rendering in concurrent mode', () => {
31 - expect(() => {
32 - ReactDOM.createRoot(document.createElement('div')).render(<App />);
33 - }).toErrorDev(
34 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
35 - 'to guarantee consistent behaviour across tests and browsers.',
36 - {withoutStack: true},
37 - );
38 - // does not warn twice
39 - expect(() => {
40 - ReactDOM.createRoot(document.createElement('div')).render(<App />);
41 - }).toErrorDev([]);
42 -});
packages/react-dom/src/client/ReactDOM.js
+1 -2
@@ -29,7 +29,6 @@ import {
29 flushControlled,
30 injectIntoDevTools,
31 IsThisRendererActing,
32 - act,
32 attemptSynchronousHydration,
33 attemptDiscreteHydration,
34 attemptContinuousHydration,
@@ -163,8 +162,8 @@ const Internals = {
162 getFiberCurrentPropsFromNode,
163 enqueueStateRestore,
164 restoreStateIfNeeded,
165 + batchedUpdates,
166 ],
167 - act,
167 // TODO: Temporary. Only used by our internal version of `act. Will remove.
168 IsThisRendererActing,
169 };
packages/react-dom/src/test-utils/ReactTestUtils.js
+7 -1
@@ -34,8 +34,14 @@ const getNodeFromInstance = EventInternals[1];
34 const getFiberCurrentPropsFromNode = EventInternals[2];
35 const enqueueStateRestore = EventInternals[3];
36 const restoreStateIfNeeded = EventInternals[4];
37 +const batchedUpdates = EventInternals[5];
38
38 -const act = SecretInternals.act;
39 +const act_notBatchedInLegacyMode = React.unstable_act;
40 +function act(callback) {
41 + return act_notBatchedInLegacyMode(() => {
42 + return batchedUpdates(callback);
43 + });
44 +}
45
46 function Event(suffix) {}
47
packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js
+9 -1
@@ -20,7 +20,7 @@ const IsThisRendererActing = SecretInternals.IsThisRendererActing;
20
21 const batchedUpdates = ReactDOM.unstable_batchedUpdates;
22
23 -const {IsSomeRendererActing} = ReactSharedInternals;
23 +const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
24
25 // This version of `act` is only used by our tests. Unlike the public version
26 // of `act`, it's designed to work identically in both production and
@@ -28,6 +28,8 @@ const {IsSomeRendererActing} = ReactSharedInternals;
28 // version, too, since our constraints in our test suite are not the same as
29 // those of developers using React — we're testing React itself, as opposed to
30 // building an app with React.
31 +// TODO: Replace the internal "concurrent" implementations of `act` with a
32 +// single shared module.
33
34 let actingUpdatesScopeDepth = 0;
35
@@ -50,8 +52,14 @@ export function unstable_concurrentAct(scope: () => Thenable<mixed> | void) {
52 IsSomeRendererActing.current = true;
53 IsThisRendererActing.current = true;
54 actingUpdatesScopeDepth++;
55 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
56 + ReactCurrentActQueue.disableActWarning = true;
57 + }
58
59 const unwind = () => {
60 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
61 + ReactCurrentActQueue.disableActWarning = false;
62 + }
63 actingUpdatesScopeDepth--;
64 IsSomeRendererActing.current = previousIsSomeRendererActing;
65 IsThisRendererActing.current = previousIsThisRendererActing;
packages/react-dom/testing.classic.fb.js
-1
@@ -9,7 +9,6 @@
9
10 export * from './index.classic.fb.js';
11 export {
12 - act,
12 createComponentSelector,
13 createHasPseudoClassSelector,
14 createRoleSelector,
packages/react-dom/testing.experimental.js
-1
@@ -8,4 +8,3 @@
8 */
9
10 export * from './index.experimental.js';
11 -export {act} from 'react-reconciler/src/ReactFiberReconciler';
packages/react-dom/testing.js
-1
@@ -9,7 +9,6 @@
9
10 export * from './index.js';
11 export {
12 - act,
12 createComponentSelector,
13 createHasPseudoClassSelector,
14 createRoleSelector,
packages/react-dom/testing.modern.fb.js
-1
@@ -9,7 +9,6 @@
9
10 export * from './index.modern.fb.js';
11 export {
12 - act,
12 createComponentSelector,
13 createHasPseudoClassSelector,
14 createRoleSelector,
packages/react-dom/testing.stable.js
-1
@@ -8,4 +8,3 @@
8 */
9
10 export * from './index.stable.js';
11 -export {act} from 'react-reconciler/src/ReactFiberReconciler';
packages/react-noop-renderer/src/createReactNoop.js
+9 -1
@@ -31,7 +31,7 @@ import {
31
32 import ReactSharedInternals from 'shared/ReactSharedInternals';
33 import enqueueTask from 'shared/enqueueTask';
34 -const {IsSomeRendererActing} = ReactSharedInternals;
34 +const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
35
36 type Container = {
37 rootID: string,
@@ -1048,6 +1048,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1048 // version, too, since our constraints in our test suite are not the same as
1049 // those of developers using React — we're testing React itself, as opposed to
1050 // building an app with React.
1051 + // TODO: Replace the internal "concurrent" implementations of `act` with a
1052 + // single shared module.
1053
1054 const {batchedUpdates, IsThisRendererActing} = NoopRenderer;
1055 let actingUpdatesScopeDepth = 0;
@@ -1071,8 +1073,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1073 IsSomeRendererActing.current = true;
1074 IsThisRendererActing.current = true;
1075 actingUpdatesScopeDepth++;
1076 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
1077 + ReactCurrentActQueue.disableActWarning = true;
1078 + }
1079
1080 const unwind = () => {
1081 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
1082 + ReactCurrentActQueue.disableActWarning = false;
1083 + }
1084 actingUpdatesScopeDepth--;
1085 IsSomeRendererActing.current = previousIsSomeRendererActing;
1086 IsThisRendererActing.current = previousIsThisRendererActing;
packages/react-reconciler/src/ReactFiberHooks.new.js
-2
@@ -79,7 +79,6 @@ import {
79 requestEventTime,
80 warnIfNotCurrentlyActingEffectsInDEV,
81 warnIfNotCurrentlyActingUpdatesInDev,
82 - warnIfNotScopedWithMatchingAct,
82 markSkippedUpdateLanes,
83 isInterleavedUpdate,
84 } from './ReactFiberWorkLoop.new';
@@ -2011,7 +2010,6 @@ function dispatchAction<S, A>(
2010 if (__DEV__) {
2011 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
2012 if ('undefined' !== typeof jest) {
2014 - warnIfNotScopedWithMatchingAct(fiber);
2013 warnIfNotCurrentlyActingUpdatesInDev(fiber);
2014 }
2015 }
packages/react-reconciler/src/ReactFiberHooks.old.js
-2
@@ -79,7 +79,6 @@ import {
79 requestEventTime,
80 warnIfNotCurrentlyActingEffectsInDEV,
81 warnIfNotCurrentlyActingUpdatesInDev,
82 - warnIfNotScopedWithMatchingAct,
82 markSkippedUpdateLanes,
83 isInterleavedUpdate,
84 } from './ReactFiberWorkLoop.old';
@@ -2011,7 +2010,6 @@ function dispatchAction<S, A>(
2010 if (__DEV__) {
2011 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
2012 if ('undefined' !== typeof jest) {
2014 - warnIfNotScopedWithMatchingAct(fiber);
2013 warnIfNotCurrentlyActingUpdatesInDev(fiber);
2014 }
2015 }
packages/react-reconciler/src/ReactFiberReconciler.js
-3
@@ -38,7 +38,6 @@ import {
38 shouldError as shouldError_old,
39 shouldSuspend as shouldSuspend_old,
40 injectIntoDevTools as injectIntoDevTools_old,
41 - act as act_old,
41 createPortal as createPortal_old,
42 createComponentSelector as createComponentSelector_old,
43 createHasPseudoClassSelector as createHasPseudoClassSelector_old,
@@ -79,7 +78,6 @@ import {
78 shouldError as shouldError_new,
79 shouldSuspend as shouldSuspend_new,
80 injectIntoDevTools as injectIntoDevTools_new,
82 - act as act_new,
81 createPortal as createPortal_new,
82 createComponentSelector as createComponentSelector_new,
83 createHasPseudoClassSelector as createHasPseudoClassSelector_new,
@@ -166,7 +164,6 @@ export const shouldSuspend = enableNewReconciler
164 export const injectIntoDevTools = enableNewReconciler
165 ? injectIntoDevTools_new
166 : injectIntoDevTools_old;
169 -export const act = enableNewReconciler ? act_new : act_old;
167 export const createPortal = enableNewReconciler
168 ? createPortal_new
169 : createPortal_old;
packages/react-reconciler/src/ReactFiberReconciler.new.js
-11
@@ -60,10 +60,7 @@ import {
60 discreteUpdates,
61 flushDiscreteUpdates,
62 flushPassiveEffects,
63 - warnIfNotScopedWithMatchingAct,
64 - warnIfUnmockedScheduler,
63 IsThisRendererActing,
66 - act,
64 } from './ReactFiberWorkLoop.new';
65 import {
66 createUpdate,
@@ -272,13 +269,6 @@ export function updateContainer(
269 }
270 const current = container.current;
271 const eventTime = requestEventTime();
275 - if (__DEV__) {
276 - // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
277 - if ('undefined' !== typeof jest) {
278 - warnIfUnmockedScheduler(current);
279 - warnIfNotScopedWithMatchingAct(current);
280 - }
281 - }
272 const lane = requestUpdateLane(current);
273
274 if (enableSchedulingProfiler) {
@@ -348,7 +338,6 @@ export {
338 flushSync,
339 flushPassiveEffects,
340 IsThisRendererActing,
351 - act,
341 };
342
343 export function getPublicRootInstance(
packages/react-reconciler/src/ReactFiberReconciler.old.js
-11
@@ -60,10 +60,7 @@ import {
60 discreteUpdates,
61 flushDiscreteUpdates,
62 flushPassiveEffects,
63 - warnIfNotScopedWithMatchingAct,
64 - warnIfUnmockedScheduler,
63 IsThisRendererActing,
66 - act,
64 } from './ReactFiberWorkLoop.old';
65 import {
66 createUpdate,
@@ -272,13 +269,6 @@ export function updateContainer(
269 }
270 const current = container.current;
271 const eventTime = requestEventTime();
275 - if (__DEV__) {
276 - // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
277 - if ('undefined' !== typeof jest) {
278 - warnIfUnmockedScheduler(current);
279 - warnIfNotScopedWithMatchingAct(current);
280 - }
281 - }
272 const lane = requestUpdateLane(current);
273
274 if (enableSchedulingProfiler) {
@@ -348,7 +338,6 @@ export {
338 flushSync,
339 flushPassiveEffects,
340 IsThisRendererActing,
351 - act,
341 };
342
343 export function getPublicRootInstance(
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+56 -298
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Thenable, Wakeable} from 'shared/ReactTypes';
10 +import type {Wakeable} from 'shared/ReactTypes';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
12 import type {Lanes, Lane} from './ReactFiberLane.new';
13 import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
@@ -24,7 +24,6 @@ import {
24 enableProfilerCommitHooks,
25 enableProfilerNestedUpdatePhase,
26 enableProfilerNestedUpdateScheduledHook,
27 - warnAboutUnmockedScheduler,
27 deferRenderPhaseUpdateToNextBatch,
28 enableDebugTracing,
29 enableSchedulingProfiler,
@@ -37,8 +36,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
36 import invariant from 'shared/invariant';
37
38 import {
40 - scheduleCallback,
41 - cancelCallback,
39 + // Aliased because `act` will override and push to an internal queue
40 + scheduleCallback as Scheduler_scheduleCallback,
41 + cancelCallback as Scheduler_cancelCallback,
42 shouldYield,
43 requestPaint,
44 now,
@@ -79,9 +79,6 @@ import {
79 markRenderStopped,
80 } from './SchedulingProfiler';
81
82 -// The scheduler is imported here *only* to detect whether it's been mocked
83 -import * as Scheduler from 'scheduler';
84 -
82 import {
83 resetAfterCommit,
84 scheduleTimeout,
@@ -238,16 +235,13 @@ import {
235 } from './ReactFiberDevToolsHook.new';
236 import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
237
241 -// Used by `act`
242 -import enqueueTask from 'shared/enqueueTask';
243 -
238 const ceil = Math.ceil;
239
240 const {
241 ReactCurrentDispatcher,
242 ReactCurrentOwner,
243 ReactCurrentBatchConfig,
250 - IsSomeRendererActing,
244 + ReactCurrentActQueue,
245 } = ReactSharedInternals;
246
247 type ExecutionContext = number;
@@ -653,7 +647,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
647
648 // Check if there's an existing task. We may be able to reuse it.
649 const existingCallbackPriority = root.callbackPriority;
656 - if (existingCallbackPriority === newCallbackPriority) {
650 + if (
651 + existingCallbackPriority === newCallbackPriority &&
652 + // Special case related to `act`. If the currently scheduled task is a
653 + // Scheduler task, rather than an `act` task, cancel it and re-scheduled
654 + // on the `act` queue.
655 + !(
656 + __DEV__ &&
657 + ReactCurrentActQueue.current !== null &&
658 + existingCallbackNode !== fakeActCallbackNode
659 + )
660 + ) {
661 if (__DEV__) {
662 // If we're going to re-use an existing task, it needs to exist.
663 // Assume that discrete update microtasks are non-cancellable and null.
@@ -2781,42 +2785,35 @@ export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void {
2785 }
2786 }
2787
2784 -export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
2788 +const fakeActCallbackNode = {};
2789 +function scheduleCallback(priorityLevel, callback) {
2790 if (__DEV__) {
2786 - if (
2787 - warnsIfNotActing === true &&
2788 - IsSomeRendererActing.current === true &&
2789 - IsThisRendererActing.current !== true
2790 - ) {
2791 - const previousFiber = ReactCurrentFiberCurrent;
2792 - try {
2793 - setCurrentDebugFiberInDEV(fiber);
2794 - console.error(
2795 - "It looks like you're using the wrong act() around your test interactions.\n" +
2796 - 'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
2797 - '// for react-dom:\n' +
2798 - // Break up imports to avoid accidentally parsing them as dependencies.
2799 - 'import {act} fr' +
2800 - "om 'react-dom/test-utils';\n" +
2801 - '// ...\n' +
2802 - 'act(() => ...);\n\n' +
2803 - '// for react-test-renderer:\n' +
2804 - // Break up imports to avoid accidentally parsing them as dependencies.
2805 - 'import TestRenderer fr' +
2806 - "om 'react-test-renderer';\n" +
2807 - 'const {act} = TestRenderer;\n' +
2808 - '// ...\n' +
2809 - 'act(() => ...);',
2810 - );
2811 - } finally {
2812 - if (previousFiber) {
2813 - setCurrentDebugFiberInDEV(fiber);
2814 - } else {
2815 - resetCurrentDebugFiberInDEV();
2816 - }
2817 - }
2791 + // If we're currently inside an `act` scope, bypass Scheduler and push to
2792 + // the `act` queue instead.
2793 + const actQueue = ReactCurrentActQueue.current;
2794 + if (actQueue !== null) {
2795 + actQueue.push(callback);
2796 + return fakeActCallbackNode;
2797 + } else {
2798 + return Scheduler_scheduleCallback(priorityLevel, callback);
2799 }
2800 + } else {
2801 + // In production, always call Scheduler. This function will be stripped out.
2802 + return Scheduler_scheduleCallback(priorityLevel, callback);
2803 + }
2804 +}
2805 +
2806 +function cancelCallback(callbackNode) {
2807 + if (__DEV__ && callbackNode === fakeActCallbackNode) {
2808 + return;
2809 }
2810 + // In production, always call Scheduler. This function will be stripped out.
2811 + return Scheduler_cancelCallback(callbackNode);
2812 +}
2813 +
2814 +function shouldForceFlushFallbacksInDEV() {
2815 + // Never force flush in production. This function should get stripped out.
2816 + return __DEV__ && ReactCurrentActQueue.current !== null;
2817 }
2818
2819 export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
@@ -2824,8 +2821,13 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
2821 if (
2822 warnsIfNotActing === true &&
2823 (fiber.mode & StrictLegacyMode) !== NoMode &&
2827 - IsSomeRendererActing.current === false &&
2828 - IsThisRendererActing.current === false
2824 + ReactCurrentActQueue.current === null &&
2825 + // Our internal tests use a custom implementation of `act` that works by
2826 + // mocking the Scheduler package. Disable the `act` warning.
2827 + // TODO: Maybe the warning should be disabled by default, and then turned
2828 + // on at the testing frameworks layer? Instead of what we do now, which
2829 + // is check if a `jest` global is defined.
2830 + ReactCurrentActQueue.disableActWarning === false
2831 ) {
2832 console.error(
2833 'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
@@ -2849,8 +2851,13 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2851 if (
2852 warnsIfNotActing === true &&
2853 executionContext === NoContext &&
2852 - IsSomeRendererActing.current === false &&
2853 - IsThisRendererActing.current === false
2854 + ReactCurrentActQueue.current === null &&
2855 + // Our internal tests use a custom implementation of `act` that works by
2856 + // mocking the Scheduler package. Disable the `act` warning.
2857 + // TODO: Maybe the warning should be disabled by default, and then turned
2858 + // on at the testing frameworks layer? Instead of what we do now, which
2859 + // is check if a `jest` global is defined.
2860 + ReactCurrentActQueue.disableActWarning === false
2861 ) {
2862 const previousFiber = ReactCurrentFiberCurrent;
2863 try {
@@ -2880,252 +2887,3 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2887 }
2888
2889 export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2883 -
2884 -// In tests, we want to enforce a mocked scheduler.
2885 -let didWarnAboutUnmockedScheduler = false;
2886 -// TODO Before we release concurrent mode, revisit this and decide whether a mocked
2887 -// scheduler is the actual recommendation. The alternative could be a testing build,
2888 -// a new lib, or whatever; we dunno just yet. This message is for early adopters
2889 -// to get their tests right.
2890 -
2891 -export function warnIfUnmockedScheduler(fiber: Fiber) {
2892 - if (__DEV__) {
2893 - if (
2894 - didWarnAboutUnmockedScheduler === false &&
2895 - Scheduler.unstable_flushAllWithoutAsserting === undefined
2896 - ) {
2897 - if (fiber.mode & ConcurrentMode) {
2898 - didWarnAboutUnmockedScheduler = true;
2899 - console.error(
2900 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
2901 - 'to guarantee consistent behaviour across tests and browsers. ' +
2902 - 'For example, with jest: \n' +
2903 - // Break up requires to avoid accidentally parsing them as dependencies.
2904 - "jest.mock('scheduler', () => require" +
2905 - "('scheduler/unstable_mock'));\n\n" +
2906 - 'For more info, visit https://reactjs.org/link/mock-scheduler',
2907 - );
2908 - } else if (warnAboutUnmockedScheduler === true) {
2909 - didWarnAboutUnmockedScheduler = true;
2910 - console.error(
2911 - 'Starting from React v18, the "scheduler" module will need to be mocked ' +
2912 - 'to guarantee consistent behaviour across tests and browsers. ' +
2913 - 'For example, with jest: \n' +
2914 - // Break up requires to avoid accidentally parsing them as dependencies.
2915 - "jest.mock('scheduler', () => require" +
2916 - "('scheduler/unstable_mock'));\n\n" +
2917 - 'For more info, visit https://reactjs.org/link/mock-scheduler',
2918 - );
2919 - }
2920 - }
2921 - }
2922 -}
2923 -
2924 -// `act` testing API
2925 -//
2926 -// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
2927 -// access to the same internals that we do here. Some trade offs in the
2928 -// implementation no longer make sense.
2929 -
2930 -let isFlushingAct = false;
2931 -let isInsideThisAct = false;
2932 -
2933 -function shouldForceFlushFallbacksInDEV() {
2934 - // Never force flush in production. This function should get stripped out.
2935 - return __DEV__ && actingUpdatesScopeDepth > 0;
2936 -}
2937 -
2938 -const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
2939 -const isSchedulerMocked = typeof flushMockScheduler === 'function';
2940 -
2941 -// Returns whether additional work was scheduled. Caller should keep flushing
2942 -// until there's no work left.
2943 -function flushActWork(): boolean {
2944 - if (flushMockScheduler !== undefined) {
2945 - const prevIsFlushing = isFlushingAct;
2946 - isFlushingAct = true;
2947 - try {
2948 - return flushMockScheduler();
2949 - } finally {
2950 - isFlushingAct = prevIsFlushing;
2951 - }
2952 - } else {
2953 - // No mock scheduler available. However, the only type of pending work is
2954 - // passive effects, which we control. So we can flush that.
2955 - const prevIsFlushing = isFlushingAct;
2956 - isFlushingAct = true;
2957 - try {
2958 - let didFlushWork = false;
2959 - while (flushPassiveEffects()) {
2960 - didFlushWork = true;
2961 - }
2962 - return didFlushWork;
2963 - } finally {
2964 - isFlushingAct = prevIsFlushing;
2965 - }
2966 - }
2967 -}
2968 -
2969 -function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
2970 - try {
2971 - flushActWork();
2972 - enqueueTask(() => {
2973 - if (flushActWork()) {
2974 - flushWorkAndMicroTasks(onDone);
2975 - } else {
2976 - onDone();
2977 - }
2978 - });
2979 - } catch (err) {
2980 - onDone(err);
2981 - }
2982 -}
2983 -
2984 -// we track the 'depth' of the act() calls with this counter,
2985 -// so we can tell if any async act() calls try to run in parallel.
2986 -
2987 -let actingUpdatesScopeDepth = 0;
2988 -
2989 -export function act(callback: () => Thenable<mixed>): Thenable<void> {
2990 - if (!__DEV__) {
2991 - invariant(
2992 - false,
2993 - 'act(...) is not supported in production builds of React.',
2994 - );
2995 - }
2996 -
2997 - const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
2998 - actingUpdatesScopeDepth++;
2999 -
3000 - const previousIsSomeRendererActing = IsSomeRendererActing.current;
3001 - const previousIsThisRendererActing = IsThisRendererActing.current;
3002 - const previousIsInsideThisAct = isInsideThisAct;
3003 - IsSomeRendererActing.current = true;
3004 - IsThisRendererActing.current = true;
3005 - isInsideThisAct = true;
3006 -
3007 - function onDone() {
3008 - actingUpdatesScopeDepth--;
3009 - IsSomeRendererActing.current = previousIsSomeRendererActing;
3010 - IsThisRendererActing.current = previousIsThisRendererActing;
3011 - isInsideThisAct = previousIsInsideThisAct;
3012 - if (__DEV__) {
3013 - if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
3014 - // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
3015 - console.error(
3016 - 'You seem to have overlapping act() calls, this is not supported. ' +
3017 - 'Be sure to await previous act() calls before making a new one. ',
3018 - );
3019 - }
3020 - }
3021 - }
3022 -
3023 - let result;
3024 - try {
3025 - result = batchedUpdates(callback);
3026 - } catch (error) {
3027 - // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
3028 - onDone();
3029 - throw error;
3030 - }
3031 -
3032 - if (
3033 - result !== null &&
3034 - typeof result === 'object' &&
3035 - typeof result.then === 'function'
3036 - ) {
3037 - // setup a boolean that gets set to true only
3038 - // once this act() call is await-ed
3039 - let called = false;
3040 - if (__DEV__) {
3041 - if (typeof Promise !== 'undefined') {
3042 - //eslint-disable-next-line no-undef
3043 - Promise.resolve()
3044 - .then(() => {})
3045 - .then(() => {
3046 - if (called === false) {
3047 - console.error(
3048 - 'You called act(async () => ...) without await. ' +
3049 - 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
3050 - 'calls and mixing their scopes. You should - await act(async () => ...);',
3051 - );
3052 - }
3053 - });
3054 - }
3055 - }
3056 -
3057 - // in the async case, the returned thenable runs the callback, flushes
3058 - // effects and microtasks in a loop until flushPassiveEffects() === false,
3059 - // and cleans up
3060 - return {
3061 - then(resolve, reject) {
3062 - called = true;
3063 - result.then(
3064 - () => {
3065 - if (
3066 - actingUpdatesScopeDepth > 1 ||
3067 - (isSchedulerMocked === true &&
3068 - previousIsSomeRendererActing === true)
3069 - ) {
3070 - onDone();
3071 - resolve();
3072 - return;
3073 - }
3074 - // we're about to exit the act() scope,
3075 - // now's the time to flush tasks/effects
3076 - flushWorkAndMicroTasks((err: ?Error) => {
3077 - onDone();
3078 - if (err) {
3079 - reject(err);
3080 - } else {
3081 - resolve();
3082 - }
3083 - });
3084 - },
3085 - err => {
3086 - onDone();
3087 - reject(err);
3088 - },
3089 - );
3090 - },
3091 - };
3092 - } else {
3093 - if (__DEV__) {
3094 - if (result !== undefined) {
3095 - console.error(
3096 - 'The callback passed to act(...) function ' +
3097 - 'must return undefined, or a Promise. You returned %s',
3098 - result,
3099 - );
3100 - }
3101 - }
3102 -
3103 - // flush effects until none remain, and cleanup
3104 - try {
3105 - if (
3106 - actingUpdatesScopeDepth === 1 &&
3107 - (isSchedulerMocked === false || previousIsSomeRendererActing === false)
3108 - ) {
3109 - // we're about to exit the act() scope,
3110 - // now's the time to flush effects
3111 - flushActWork();
3112 - }
3113 - onDone();
3114 - } catch (err) {
3115 - onDone();
3116 - throw err;
3117 - }
3118 -
3119 - // in the sync case, the returned thenable only warns *if* await-ed
3120 - return {
3121 - then(resolve) {
3122 - if (__DEV__) {
3123 - console.error(
3124 - 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
3125 - );
3126 - }
3127 - resolve();
3128 - },
3129 - };
3130 - }
3131 -}
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+56 -298
@@ -7,7 +7,7 @@
7 * @flow
8 */
9
10 -import type {Thenable, Wakeable} from 'shared/ReactTypes';
10 +import type {Wakeable} from 'shared/ReactTypes';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
12 import type {Lanes, Lane} from './ReactFiberLane.old';
13 import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
@@ -24,7 +24,6 @@ import {
24 enableProfilerCommitHooks,
25 enableProfilerNestedUpdatePhase,
26 enableProfilerNestedUpdateScheduledHook,
27 - warnAboutUnmockedScheduler,
27 deferRenderPhaseUpdateToNextBatch,
28 enableDebugTracing,
29 enableSchedulingProfiler,
@@ -37,8 +36,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
36 import invariant from 'shared/invariant';
37
38 import {
40 - scheduleCallback,
41 - cancelCallback,
39 + // Aliased because `act` will override and push to an internal queue
40 + scheduleCallback as Scheduler_scheduleCallback,
41 + cancelCallback as Scheduler_cancelCallback,
42 shouldYield,
43 requestPaint,
44 now,
@@ -79,9 +79,6 @@ import {
79 markRenderStopped,
80 } from './SchedulingProfiler';
81
82 -// The scheduler is imported here *only* to detect whether it's been mocked
83 -import * as Scheduler from 'scheduler';
84 -
82 import {
83 resetAfterCommit,
84 scheduleTimeout,
@@ -238,16 +235,13 @@ import {
235 } from './ReactFiberDevToolsHook.old';
236 import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
237
241 -// Used by `act`
242 -import enqueueTask from 'shared/enqueueTask';
243 -
238 const ceil = Math.ceil;
239
240 const {
241 ReactCurrentDispatcher,
242 ReactCurrentOwner,
243 ReactCurrentBatchConfig,
250 - IsSomeRendererActing,
244 + ReactCurrentActQueue,
245 } = ReactSharedInternals;
246
247 type ExecutionContext = number;
@@ -653,7 +647,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
647
648 // Check if there's an existing task. We may be able to reuse it.
649 const existingCallbackPriority = root.callbackPriority;
656 - if (existingCallbackPriority === newCallbackPriority) {
650 + if (
651 + existingCallbackPriority === newCallbackPriority &&
652 + // Special case related to `act`. If the currently scheduled task is a
653 + // Scheduler task, rather than an `act` task, cancel it and re-scheduled
654 + // on the `act` queue.
655 + !(
656 + __DEV__ &&
657 + ReactCurrentActQueue.current !== null &&
658 + existingCallbackNode !== fakeActCallbackNode
659 + )
660 + ) {
661 if (__DEV__) {
662 // If we're going to re-use an existing task, it needs to exist.
663 // Assume that discrete update microtasks are non-cancellable and null.
@@ -2781,42 +2785,35 @@ export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void {
2785 }
2786 }
2787
2784 -export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
2788 +const fakeActCallbackNode = {};
2789 +function scheduleCallback(priorityLevel, callback) {
2790 if (__DEV__) {
2786 - if (
2787 - warnsIfNotActing === true &&
2788 - IsSomeRendererActing.current === true &&
2789 - IsThisRendererActing.current !== true
2790 - ) {
2791 - const previousFiber = ReactCurrentFiberCurrent;
2792 - try {
2793 - setCurrentDebugFiberInDEV(fiber);
2794 - console.error(
2795 - "It looks like you're using the wrong act() around your test interactions.\n" +
2796 - 'Be sure to use the matching version of act() corresponding to your renderer:\n\n' +
2797 - '// for react-dom:\n' +
2798 - // Break up imports to avoid accidentally parsing them as dependencies.
2799 - 'import {act} fr' +
2800 - "om 'react-dom/test-utils';\n" +
2801 - '// ...\n' +
2802 - 'act(() => ...);\n\n' +
2803 - '// for react-test-renderer:\n' +
2804 - // Break up imports to avoid accidentally parsing them as dependencies.
2805 - 'import TestRenderer fr' +
2806 - "om 'react-test-renderer';\n" +
2807 - 'const {act} = TestRenderer;\n' +
2808 - '// ...\n' +
2809 - 'act(() => ...);',
2810 - );
2811 - } finally {
2812 - if (previousFiber) {
2813 - setCurrentDebugFiberInDEV(fiber);
2814 - } else {
2815 - resetCurrentDebugFiberInDEV();
2816 - }
2817 - }
2791 + // If we're currently inside an `act` scope, bypass Scheduler and push to
2792 + // the `act` queue instead.
2793 + const actQueue = ReactCurrentActQueue.current;
2794 + if (actQueue !== null) {
2795 + actQueue.push(callback);
2796 + return fakeActCallbackNode;
2797 + } else {
2798 + return Scheduler_scheduleCallback(priorityLevel, callback);
2799 }
2800 + } else {
2801 + // In production, always call Scheduler. This function will be stripped out.
2802 + return Scheduler_scheduleCallback(priorityLevel, callback);
2803 + }
2804 +}
2805 +
2806 +function cancelCallback(callbackNode) {
2807 + if (__DEV__ && callbackNode === fakeActCallbackNode) {
2808 + return;
2809 }
2810 + // In production, always call Scheduler. This function will be stripped out.
2811 + return Scheduler_cancelCallback(callbackNode);
2812 +}
2813 +
2814 +function shouldForceFlushFallbacksInDEV() {
2815 + // Never force flush in production. This function should get stripped out.
2816 + return __DEV__ && ReactCurrentActQueue.current !== null;
2817 }
2818
2819 export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
@@ -2824,8 +2821,13 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
2821 if (
2822 warnsIfNotActing === true &&
2823 (fiber.mode & StrictLegacyMode) !== NoMode &&
2827 - IsSomeRendererActing.current === false &&
2828 - IsThisRendererActing.current === false
2824 + ReactCurrentActQueue.current === null &&
2825 + // Our internal tests use a custom implementation of `act` that works by
2826 + // mocking the Scheduler package. Disable the `act` warning.
2827 + // TODO: Maybe the warning should be disabled by default, and then turned
2828 + // on at the testing frameworks layer? Instead of what we do now, which
2829 + // is check if a `jest` global is defined.
2830 + ReactCurrentActQueue.disableActWarning === false
2831 ) {
2832 console.error(
2833 'An update to %s ran an effect, but was not wrapped in act(...).\n\n' +
@@ -2849,8 +2851,13 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2851 if (
2852 warnsIfNotActing === true &&
2853 executionContext === NoContext &&
2852 - IsSomeRendererActing.current === false &&
2853 - IsThisRendererActing.current === false
2854 + ReactCurrentActQueue.current === null &&
2855 + // Our internal tests use a custom implementation of `act` that works by
2856 + // mocking the Scheduler package. Disable the `act` warning.
2857 + // TODO: Maybe the warning should be disabled by default, and then turned
2858 + // on at the testing frameworks layer? Instead of what we do now, which
2859 + // is check if a `jest` global is defined.
2860 + ReactCurrentActQueue.disableActWarning === false
2861 ) {
2862 const previousFiber = ReactCurrentFiberCurrent;
2863 try {
@@ -2880,252 +2887,3 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2887 }
2888
2889 export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2883 -
2884 -// In tests, we want to enforce a mocked scheduler.
2885 -let didWarnAboutUnmockedScheduler = false;
2886 -// TODO Before we release concurrent mode, revisit this and decide whether a mocked
2887 -// scheduler is the actual recommendation. The alternative could be a testing build,
2888 -// a new lib, or whatever; we dunno just yet. This message is for early adopters
2889 -// to get their tests right.
2890 -
2891 -export function warnIfUnmockedScheduler(fiber: Fiber) {
2892 - if (__DEV__) {
2893 - if (
2894 - didWarnAboutUnmockedScheduler === false &&
2895 - Scheduler.unstable_flushAllWithoutAsserting === undefined
2896 - ) {
2897 - if (fiber.mode & ConcurrentMode) {
2898 - didWarnAboutUnmockedScheduler = true;
2899 - console.error(
2900 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
2901 - 'to guarantee consistent behaviour across tests and browsers. ' +
2902 - 'For example, with jest: \n' +
2903 - // Break up requires to avoid accidentally parsing them as dependencies.
2904 - "jest.mock('scheduler', () => require" +
2905 - "('scheduler/unstable_mock'));\n\n" +
2906 - 'For more info, visit https://reactjs.org/link/mock-scheduler',
2907 - );
2908 - } else if (warnAboutUnmockedScheduler === true) {
2909 - didWarnAboutUnmockedScheduler = true;
2910 - console.error(
2911 - 'Starting from React v18, the "scheduler" module will need to be mocked ' +
2912 - 'to guarantee consistent behaviour across tests and browsers. ' +
2913 - 'For example, with jest: \n' +
2914 - // Break up requires to avoid accidentally parsing them as dependencies.
2915 - "jest.mock('scheduler', () => require" +
2916 - "('scheduler/unstable_mock'));\n\n" +
2917 - 'For more info, visit https://reactjs.org/link/mock-scheduler',
2918 - );
2919 - }
2920 - }
2921 - }
2922 -}
2923 -
2924 -// `act` testing API
2925 -//
2926 -// TODO: This is mostly a copy-paste from the legacy `act`, which does not have
2927 -// access to the same internals that we do here. Some trade offs in the
2928 -// implementation no longer make sense.
2929 -
2930 -let isFlushingAct = false;
2931 -let isInsideThisAct = false;
2932 -
2933 -function shouldForceFlushFallbacksInDEV() {
2934 - // Never force flush in production. This function should get stripped out.
2935 - return __DEV__ && actingUpdatesScopeDepth > 0;
2936 -}
2937 -
2938 -const flushMockScheduler = Scheduler.unstable_flushAllWithoutAsserting;
2939 -const isSchedulerMocked = typeof flushMockScheduler === 'function';
2940 -
2941 -// Returns whether additional work was scheduled. Caller should keep flushing
2942 -// until there's no work left.
2943 -function flushActWork(): boolean {
2944 - if (flushMockScheduler !== undefined) {
2945 - const prevIsFlushing = isFlushingAct;
2946 - isFlushingAct = true;
2947 - try {
2948 - return flushMockScheduler();
2949 - } finally {
2950 - isFlushingAct = prevIsFlushing;
2951 - }
2952 - } else {
2953 - // No mock scheduler available. However, the only type of pending work is
2954 - // passive effects, which we control. So we can flush that.
2955 - const prevIsFlushing = isFlushingAct;
2956 - isFlushingAct = true;
2957 - try {
2958 - let didFlushWork = false;
2959 - while (flushPassiveEffects()) {
2960 - didFlushWork = true;
2961 - }
2962 - return didFlushWork;
2963 - } finally {
2964 - isFlushingAct = prevIsFlushing;
2965 - }
2966 - }
2967 -}
2968 -
2969 -function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
2970 - try {
2971 - flushActWork();
2972 - enqueueTask(() => {
2973 - if (flushActWork()) {
2974 - flushWorkAndMicroTasks(onDone);
2975 - } else {
2976 - onDone();
2977 - }
2978 - });
2979 - } catch (err) {
2980 - onDone(err);
2981 - }
2982 -}
2983 -
2984 -// we track the 'depth' of the act() calls with this counter,
2985 -// so we can tell if any async act() calls try to run in parallel.
2986 -
2987 -let actingUpdatesScopeDepth = 0;
2988 -
2989 -export function act(callback: () => Thenable<mixed>): Thenable<void> {
2990 - if (!__DEV__) {
2991 - invariant(
2992 - false,
2993 - 'act(...) is not supported in production builds of React.',
2994 - );
2995 - }
2996 -
2997 - const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
2998 - actingUpdatesScopeDepth++;
2999 -
3000 - const previousIsSomeRendererActing = IsSomeRendererActing.current;
3001 - const previousIsThisRendererActing = IsThisRendererActing.current;
3002 - const previousIsInsideThisAct = isInsideThisAct;
3003 - IsSomeRendererActing.current = true;
3004 - IsThisRendererActing.current = true;
3005 - isInsideThisAct = true;
3006 -
3007 - function onDone() {
3008 - actingUpdatesScopeDepth--;
3009 - IsSomeRendererActing.current = previousIsSomeRendererActing;
3010 - IsThisRendererActing.current = previousIsThisRendererActing;
3011 - isInsideThisAct = previousIsInsideThisAct;
3012 - if (__DEV__) {
3013 - if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
3014 - // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
3015 - console.error(
3016 - 'You seem to have overlapping act() calls, this is not supported. ' +
3017 - 'Be sure to await previous act() calls before making a new one. ',
3018 - );
3019 - }
3020 - }
3021 - }
3022 -
3023 - let result;
3024 - try {
3025 - result = batchedUpdates(callback);
3026 - } catch (error) {
3027 - // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth
3028 - onDone();
3029 - throw error;
3030 - }
3031 -
3032 - if (
3033 - result !== null &&
3034 - typeof result === 'object' &&
3035 - typeof result.then === 'function'
3036 - ) {
3037 - // setup a boolean that gets set to true only
3038 - // once this act() call is await-ed
3039 - let called = false;
3040 - if (__DEV__) {
3041 - if (typeof Promise !== 'undefined') {
3042 - //eslint-disable-next-line no-undef
3043 - Promise.resolve()
3044 - .then(() => {})
3045 - .then(() => {
3046 - if (called === false) {
3047 - console.error(
3048 - 'You called act(async () => ...) without await. ' +
3049 - 'This could lead to unexpected testing behaviour, interleaving multiple act ' +
3050 - 'calls and mixing their scopes. You should - await act(async () => ...);',
3051 - );
3052 - }
3053 - });
3054 - }
3055 - }
3056 -
3057 - // in the async case, the returned thenable runs the callback, flushes
3058 - // effects and microtasks in a loop until flushPassiveEffects() === false,
3059 - // and cleans up
3060 - return {
3061 - then(resolve, reject) {
3062 - called = true;
3063 - result.then(
3064 - () => {
3065 - if (
3066 - actingUpdatesScopeDepth > 1 ||
3067 - (isSchedulerMocked === true &&
3068 - previousIsSomeRendererActing === true)
3069 - ) {
3070 - onDone();
3071 - resolve();
3072 - return;
3073 - }
3074 - // we're about to exit the act() scope,
3075 - // now's the time to flush tasks/effects
3076 - flushWorkAndMicroTasks((err: ?Error) => {
3077 - onDone();
3078 - if (err) {
3079 - reject(err);
3080 - } else {
3081 - resolve();
3082 - }
3083 - });
3084 - },
3085 - err => {
3086 - onDone();
3087 - reject(err);
3088 - },
3089 - );
3090 - },
3091 - };
3092 - } else {
3093 - if (__DEV__) {
3094 - if (result !== undefined) {
3095 - console.error(
3096 - 'The callback passed to act(...) function ' +
3097 - 'must return undefined, or a Promise. You returned %s',
3098 - result,
3099 - );
3100 - }
3101 - }
3102 -
3103 - // flush effects until none remain, and cleanup
3104 - try {
3105 - if (
3106 - actingUpdatesScopeDepth === 1 &&
3107 - (isSchedulerMocked === false || previousIsSomeRendererActing === false)
3108 - ) {
3109 - // we're about to exit the act() scope,
3110 - // now's the time to flush effects
3111 - flushActWork();
3112 - }
3113 - onDone();
3114 - } catch (err) {
3115 - onDone();
3116 - throw err;
3117 - }
3118 -
3119 - // in the sync case, the returned thenable only warns *if* await-ed
3120 - return {
3121 - then(resolve) {
3122 - if (__DEV__) {
3123 - console.error(
3124 - 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
3125 - );
3126 - }
3127 - resolve();
3128 - },
3129 - };
3130 - }
3131 -}
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+4 -2
@@ -11,6 +11,7 @@
11 'use strict';
12
13 let React;
14 +let act;
15 let ReactFiberReconciler;
16 let ConcurrentRoot;
17 let DefaultEventPriority;
@@ -19,6 +20,7 @@ describe('ReactFiberHostContext', () => {
20 beforeEach(() => {
21 jest.resetModules();
22 React = require('react');
23 + act = React.unstable_act;
24 ReactFiberReconciler = require('react-reconciler');
25 ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
26 .ConcurrentRoot;
@@ -71,7 +73,7 @@ describe('ReactFiberHostContext', () => {
73 false,
74 null,
75 );
74 - Renderer.act(() => {
76 + act(() => {
77 Renderer.updateContainer(
78 <a>
79 <b />
@@ -132,7 +134,7 @@ describe('ReactFiberHostContext', () => {
134 false,
135 null,
136 );
135 - Renderer.act(() => {
137 + act(() => {
138 Renderer.updateContainer(
139 <a>
140 <b />
packages/react-test-renderer/src/ReactTestRenderer.js
+17 -2
@@ -12,6 +12,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
12 import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
13 import type {Instance, TextInstance} from './ReactTestHostConfig';
14
15 +import * as React from 'react';
16 import * as Scheduler from 'scheduler/unstable_mock';
17 import {
18 getPublicRootInstance,
@@ -20,7 +21,6 @@ import {
21 flushSync,
22 injectIntoDevTools,
23 batchedUpdates,
23 - act,
24 IsThisRendererActing,
25 } from 'react-reconciler/src/ReactFiberReconciler';
26 import {findCurrentFiberUsingSlowPath} from 'react-reconciler/src/ReactFiberTreeReflection';
@@ -53,7 +53,14 @@ import {getPublicInstance} from './ReactTestHostConfig';
53 import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
54 import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
55
56 -const {IsSomeRendererActing} = ReactSharedInternals;
56 +const {IsSomeRendererActing, ReactCurrentActQueue} = ReactSharedInternals;
57 +
58 +const act_notBatchedInLegacyMode = React.unstable_act;
59 +function act(callback: () => Thenable<mixed>): Thenable<void> {
60 + return act_notBatchedInLegacyMode(() => {
61 + return batchedUpdates(callback);
62 + });
63 +}
64
65 type TestRendererOptions = {
66 createNodeMock: (element: React$Element<any>) => any,
@@ -604,6 +611,8 @@ let actingUpdatesScopeDepth = 0;
611 // building an app with React.
612 // TODO: Migrate our tests to use ReactNoop. Although we would need to figure
613 // out a solution for Relay, which has some Concurrent Mode tests.
614 +// TODO: Replace the internal "concurrent" implementations of `act` with a
615 +// single shared module.
616 function unstable_concurrentAct(scope: () => Thenable<mixed> | void) {
617 if (Scheduler.unstable_flushAllWithoutAsserting === undefined) {
618 throw Error(
@@ -623,8 +632,14 @@ function unstable_concurrentAct(scope: () => Thenable<mixed> | void) {
632 IsSomeRendererActing.current = true;
633 IsThisRendererActing.current = true;
634 actingUpdatesScopeDepth++;
635 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
636 + ReactCurrentActQueue.disableActWarning = true;
637 + }
638
639 const unwind = () => {
640 + if (__DEV__ && actingUpdatesScopeDepth === 1) {
641 + ReactCurrentActQueue.disableActWarning = false;
642 + }
643 actingUpdatesScopeDepth--;
644 IsSomeRendererActing.current = previousIsSomeRendererActing;
645 IsThisRendererActing.current = previousIsThisRendererActing;
packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
-17
@@ -40,23 +40,6 @@ describe('ReactTestRenderer.act()', () => {
40 expect(root.toJSON()).toEqual('1');
41 });
42
43 - it("warns if you don't use .act", () => {
44 - let setCtr;
45 - function App(props) {
46 - const [ctr, _setCtr] = React.useState(0);
47 - setCtr = _setCtr;
48 - return ctr;
49 - }
50 -
51 - ReactTestRenderer.create(<App />);
52 -
53 - expect(() => {
54 - setCtr(1);
55 - }).toErrorDev([
56 - 'An update to App inside a test was not wrapped in act(...)',
57 - ]);
58 - });
59 -
43 describe('async', () => {
44 // @gate __DEV__
45 it('should work with async/await', async () => {
packages/react/index.classic.fb.js
+1
@@ -9,6 +9,7 @@
9
10 export {
11 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
12 + act as unstable_act,
13 Children,
14 Component,
15 Fragment,
packages/react/index.experimental.js
+1
@@ -9,6 +9,7 @@
9
10 export {
11 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
12 + act as unstable_act,
13 Children,
14 Component,
15 Fragment,
packages/react/index.js
+1
@@ -33,6 +33,7 @@ export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
33 // We can't use export * from in Flow for some reason.
34 export {
35 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
36 + act as unstable_act,
37 Children,
38 Component,
39 Fragment,
packages/react/index.modern.fb.js
+1
@@ -9,6 +9,7 @@
9
10 export {
11 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
12 + act as unstable_act,
13 Children,
14 Component,
15 Fragment,
packages/react/index.stable.js
+1
@@ -9,6 +9,7 @@
9
10 export {
11 __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
12 + act as unstable_act,
13 Children,
14 Component,
15 Fragment,
packages/react/src/React.js
+2
@@ -60,6 +60,7 @@ import {
60 import {createMutableSource} from './ReactMutableSource';
61 import ReactSharedInternals from './ReactSharedInternals';
62 import {startTransition} from './ReactStartTransition';
63 +import {act} from './ReactAct';
64
65 // TODO: Move this branching into the other module instead and just re-export.
66 const createElement = __DEV__ ? createElementWithValidation : createElementProd;
@@ -120,4 +121,5 @@ export {
121 // enableScopeAPI
122 REACT_SCOPE_TYPE as unstable_Scope,
123 useOpaqueIdentifier as unstable_useOpaqueIdentifier,
124 + act,
125 };
packages/react/src/ReactAct.js new
+194
@@ -0,0 +1,194 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Thenable} from 'shared/ReactTypes';
11 +import ReactCurrentActQueue from './ReactCurrentActQueue';
12 +import invariant from 'shared/invariant';
13 +import enqueueTask from 'shared/enqueueTask';
14 +
15 +let actScopeDepth = 0;
16 +let didWarnNoAwaitAct = false;
17 +
18 +export function act(callback: () => Thenable<mixed>): Thenable<void> {
19 + if (__DEV__) {
20 + // `act` calls can be nested, so we track the depth. This represents the
21 + // number of `act` scopes on the stack.
22 + const prevActScopeDepth = actScopeDepth;
23 + actScopeDepth++;
24 +
25 + if (ReactCurrentActQueue.current === null) {
26 + // This is the outermost `act` scope. Initialize the queue. The reconciler
27 + // will detect the queue and use it instead of Scheduler.
28 + ReactCurrentActQueue.current = [];
29 + }
30 +
31 + let result;
32 + try {
33 + result = callback();
34 + } catch (error) {
35 + popActScope(prevActScopeDepth);
36 + throw error;
37 + }
38 +
39 + if (
40 + result !== null &&
41 + typeof result === 'object' &&
42 + typeof result.then === 'function'
43 + ) {
44 + // The callback is an async function (i.e. returned a promise). Wait
45 + // for it to resolve before exiting the current scope.
46 + let wasAwaited = false;
47 + const thenable = {
48 + then(resolve, reject) {
49 + wasAwaited = true;
50 + result.then(
51 + () => {
52 + popActScope(prevActScopeDepth);
53 + if (actScopeDepth === 0) {
54 + // We've exited the outermost act scope. Recursively flush the
55 + // queue until there's no remaining work.
56 + recursivelyFlushAsyncActWork(resolve, reject);
57 + } else {
58 + resolve();
59 + }
60 + },
61 + error => {
62 + // The callback threw an error.
63 + popActScope(prevActScopeDepth);
64 + reject(error);
65 + },
66 + );
67 + },
68 + };
69 +
70 + if (__DEV__) {
71 + if (!didWarnNoAwaitAct && typeof Promise !== 'undefined') {
72 + // eslint-disable-next-line no-undef
73 + Promise.resolve()
74 + .then(() => {})
75 + .then(() => {
76 + if (!wasAwaited) {
77 + didWarnNoAwaitAct = true;
78 + console.error(
79 + 'You called act(async () => ...) without await. ' +
80 + 'This could lead to unexpected testing behaviour, ' +
81 + 'interleaving multiple act calls and mixing their ' +
82 + 'scopes. ' +
83 + 'You should - await act(async () => ...);',
84 + );
85 + }
86 + });
87 + }
88 + }
89 + return thenable;
90 + } else {
91 + // The callback is not an async function. Exit the current scope
92 + // immediately, without awaiting.
93 + popActScope(prevActScopeDepth);
94 + if (actScopeDepth === 0) {
95 + // Exiting the outermost act scope. Flush the queue.
96 + const queue = ReactCurrentActQueue.current;
97 + if (queue !== null) {
98 + flushActQueue(queue);
99 + ReactCurrentActQueue.current = null;
100 + }
101 + // Return a thenable. If the user awaits it, we'll flush again in
102 + // case additional work was scheduled by a microtask.
103 + return {
104 + then(resolve, reject) {
105 + // Confirm we haven't re-entered another `act` scope, in case
106 + // the user does something weird like await the thenable
107 + // multiple times.
108 + if (ReactCurrentActQueue.current === null) {
109 + // Recursively flush the queue until there's no remaining work.
110 + ReactCurrentActQueue.current = [];
111 + recursivelyFlushAsyncActWork(resolve, reject);
112 + }
113 + },
114 + };
115 + } else {
116 + // Since we're inside a nested `act` scope, the returned thenable
117 + // immediately resolves. The outer scope will flush the queue.
118 + return {
119 + then(resolve, reject) {
120 + resolve();
121 + },
122 + };
123 + }
124 + }
125 + } else {
126 + invariant(
127 + false,
128 + 'act(...) is not supported in production builds of React.',
129 + );
130 + }
131 +}
132 +
133 +function popActScope(prevActScopeDepth) {
134 + if (__DEV__) {
135 + if (prevActScopeDepth !== actScopeDepth - 1) {
136 + console.error(
137 + 'You seem to have overlapping act() calls, this is not supported. ' +
138 + 'Be sure to await previous act() calls before making a new one. ',
139 + );
140 + }
141 + actScopeDepth = prevActScopeDepth;
142 + }
143 +}
144 +
145 +function recursivelyFlushAsyncActWork(resolve, reject) {
146 + if (__DEV__) {
147 + const queue = ReactCurrentActQueue.current;
148 + if (queue !== null) {
149 + try {
150 + flushActQueue(queue);
151 + enqueueTask(() => {
152 + if (queue.length === 0) {
153 + // No additional work was scheduled. Finish.
154 + ReactCurrentActQueue.current = null;
155 + resolve();
156 + } else {
157 + // Keep flushing work until there's none left.
158 + recursivelyFlushAsyncActWork(resolve, reject);
159 + }
160 + });
161 + } catch (error) {
162 + reject(error);
163 + }
164 + } else {
165 + resolve();
166 + }
167 + }
168 +}
169 +
170 +let isFlushing = false;
171 +function flushActQueue(queue) {
172 + if (__DEV__) {
173 + if (!isFlushing) {
174 + // Prevent re-entrancy.
175 + isFlushing = true;
176 + let i = 0;
177 + try {
178 + for (; i < queue.length; i++) {
179 + let callback = queue[i];
180 + do {
181 + callback = callback(true);
182 + } while (callback !== null);
183 + }
184 + queue.length = 0;
185 + } catch (error) {
186 + // If something throws, leave the remaining callbacks on the queue.
187 + queue = queue.slice(i + 1);
188 + throw error;
189 + } finally {
190 + isFlushing = false;
191 + }
192 + }
193 + }
194 +}
packages/react/src/ReactCurrentActQueue.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +type RendererTask = boolean => RendererTask | null;
11 +
12 +const ReactCurrentActQueue = {
13 + current: (null: null | Array<RendererTask>),
14 + // Our internal tests use a custom implementation of `act` that works by
15 + // mocking the Scheduler package. Use this field to disable the `act` warning.
16 + // TODO: Maybe the warning should be disabled by default, and then turned
17 + // on at the testing frameworks layer? Instead of what we do now, which
18 + // is check if a `jest` global is defined.
19 + disableActWarning: (false: boolean),
20 +};
21 +
22 +export default ReactCurrentActQueue;
packages/react/src/ReactSharedInternals.js
+2
@@ -8,6 +8,7 @@
8 import assign from 'object-assign';
9 import ReactCurrentDispatcher from './ReactCurrentDispatcher';
10 import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
11 +import ReactCurrentActQueue from './ReactCurrentActQueue';
12 import ReactCurrentOwner from './ReactCurrentOwner';
13 import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
14 import IsSomeRendererActing from './IsSomeRendererActing';
@@ -23,6 +24,7 @@ const ReactSharedInternals = {
24
25 if (__DEV__) {
26 ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame;
27 + ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue;
28 }
29
30 export default ReactSharedInternals;
packages/shared/ReactFeatureFlags.js
-1
@@ -78,7 +78,6 @@ export const enableCreateEventHandleAPI = false;
78
79 // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v18?)
80 // Till then, we warn about the missing mock, but still fallback to a legacy mode compatible version
81 -export const warnAboutUnmockedScheduler = false;
81
82 // Add a callback property to suspense to notify which promises are currently
83 // in the update queue. This allows reporting and tracing of what is causing
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -30,7 +30,6 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
30 export const warnAboutDeprecatedLifecycles = true;
31 export const enableScopeAPI = false;
32 export const enableCreateEventHandleAPI = false;
33 -export const warnAboutUnmockedScheduler = true;
33 export const enableSuspenseCallback = false;
34 export const warnAboutDefaultPropsOnFunctionComponents = false;
35 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
29 export const enableSchedulerDebugging = false;
30 export const enableScopeAPI = false;
31 export const enableCreateEventHandleAPI = false;
32 -export const warnAboutUnmockedScheduler = false;
32 export const enableSuspenseCallback = false;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
29 export const enableSchedulerDebugging = false;
30 export const enableScopeAPI = false;
31 export const enableCreateEventHandleAPI = false;
32 -export const warnAboutUnmockedScheduler = false;
32 export const enableSuspenseCallback = false;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
-1
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
29 export const enableSchedulerDebugging = false;
30 export const enableScopeAPI = false;
31 export const enableCreateEventHandleAPI = false;
32 -export const warnAboutUnmockedScheduler = false;
32 export const enableSuspenseCallback = false;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -29,7 +29,6 @@ export const disableJavaScriptURLs = false;
29 export const disableInputAttributeSyncing = false;
30 export const enableScopeAPI = true;
31 export const enableCreateEventHandleAPI = false;
32 -export const warnAboutUnmockedScheduler = true;
32 export const enableSuspenseCallback = true;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.testing.js
-1
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
29 export const enableSchedulerDebugging = false;
30 export const enableScopeAPI = false;
31 export const enableCreateEventHandleAPI = false;
32 -export const warnAboutUnmockedScheduler = false;
32 export const enableSuspenseCallback = false;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
-1
@@ -29,7 +29,6 @@ export const disableInputAttributeSyncing = false;
29 export const enableSchedulerDebugging = false;
30 export const enableScopeAPI = true;
31 export const enableCreateEventHandleAPI = true;
32 -export const warnAboutUnmockedScheduler = true;
32 export const enableSuspenseCallback = true;
33 export const warnAboutDefaultPropsOnFunctionComponents = false;
34 export const warnAboutStringRefs = false;
packages/shared/forks/ReactFeatureFlags.www.js
-2
@@ -75,8 +75,6 @@ export const enableCreateEventHandleAPI = true;
75
76 export const enableScopeAPI = true;
77
78 -export const warnAboutUnmockedScheduler = true;
79 -
78 export const enableSuspenseCallback = true;
79
80 export const enableComponentStackLocations = true;
scripts/jest/setupTests.www.js
-1
@@ -14,7 +14,6 @@ jest.mock('shared/ReactFeatureFlags', () => {
14 // www configuration. Update those tests so that they work against the www
15 // configuration, too. Then remove these overrides.
16 wwwFlags.disableLegacyContext = defaultFlags.disableLegacyContext;
17 - wwwFlags.warnAboutUnmockedScheduler = defaultFlags.warnAboutUnmockedScheduler;
17 wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs;
18
19 return wwwFlags;