Skip to content

Commit bd0a963

Browse files
authored
Throw when act is used in production (#21686)
Upgrades the deprecation warning to a runtime error. I did it this way instead of removing the export so the type is the same in both builds. It will get dead code eliminated regardless.
1 parent a0d2d1e commit bd0a963

File tree

15 files changed

+87
-92
lines changed

15 files changed

+87
-92
lines changed

fixtures/legacy-jsx-runtimes/setupTests.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ function shouldIgnoreConsoleError(format, args) {
3333
// They are noisy too so we'll try to ignore them.
3434
return true;
3535
}
36-
if (
37-
format.indexOf(
38-
'act(...) is not supported in production builds of React'
39-
) === 0
40-
) {
41-
// We don't yet support act() for prod builds, and warn for it.
42-
// But we'd like to use act() ourselves for prod builds.
43-
// Let's ignore the warning and #yolo.
44-
return true;
45-
}
4636
}
4737
// Looks legit
4838
return false;

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

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function runActTests(label, render, unmount, rerender) {
135135
});
136136

137137
describe('sync', () => {
138+
// @gate __DEV__
138139
it('can use act to flush effects', () => {
139140
function App() {
140141
React.useEffect(() => {
@@ -150,6 +151,7 @@ function runActTests(label, render, unmount, rerender) {
150151
expect(Scheduler).toHaveYielded([100]);
151152
});
152153

154+
// @gate __DEV__
153155
it('flushes effects on every call', async () => {
154156
function App() {
155157
const [ctr, setCtr] = React.useState(0);
@@ -186,6 +188,7 @@ function runActTests(label, render, unmount, rerender) {
186188
expect(button.innerHTML).toBe('5');
187189
});
188190

191+
// @gate __DEV__
189192
it("should keep flushing effects until they're done", () => {
190193
function App() {
191194
const [ctr, setCtr] = React.useState(0);
@@ -204,6 +207,7 @@ function runActTests(label, render, unmount, rerender) {
204207
expect(container.innerHTML).toBe('5');
205208
});
206209

210+
// @gate __DEV__
207211
it('should flush effects only on exiting the outermost act', () => {
208212
function App() {
209213
React.useEffect(() => {
@@ -224,6 +228,7 @@ function runActTests(label, render, unmount, rerender) {
224228
expect(Scheduler).toHaveYielded([0]);
225229
});
226230

231+
// @gate __DEV__
227232
it('warns if a setState is called outside of act(...)', () => {
228233
let setValue = null;
229234
function App() {
@@ -250,6 +255,7 @@ function runActTests(label, render, unmount, rerender) {
250255
jest.useRealTimers();
251256
});
252257

258+
// @gate __DEV__
253259
it('lets a ticker update', () => {
254260
function App() {
255261
const [toggle, setToggle] = React.useState(0);
@@ -272,6 +278,7 @@ function runActTests(label, render, unmount, rerender) {
272278
expect(container.innerHTML).toBe('1');
273279
});
274280

281+
// @gate __DEV__
275282
it('can use the async version to catch microtasks', async () => {
276283
function App() {
277284
const [toggle, setToggle] = React.useState(0);
@@ -294,6 +301,7 @@ function runActTests(label, render, unmount, rerender) {
294301
expect(container.innerHTML).toBe('1');
295302
});
296303

304+
// @gate __DEV__
297305
it('can handle cascading promises with fake timers', async () => {
298306
// this component triggers an effect, that waits a tick,
299307
// then sets state. repeats this 5 times.
@@ -317,6 +325,7 @@ function runActTests(label, render, unmount, rerender) {
317325
expect(container.innerHTML).toBe('5');
318326
});
319327

328+
// @gate __DEV__
320329
it('flushes immediate re-renders with act', () => {
321330
function App() {
322331
const [ctr, setCtr] = React.useState(0);
@@ -346,6 +355,7 @@ function runActTests(label, render, unmount, rerender) {
346355
});
347356
});
348357

358+
// @gate __DEV__
349359
it('warns if you return a value inside act', () => {
350360
expect(() => act(() => null)).toErrorDev(
351361
[
@@ -361,6 +371,7 @@ function runActTests(label, render, unmount, rerender) {
361371
);
362372
});
363373

374+
// @gate __DEV__
364375
it('warns if you try to await a sync .act call', () => {
365376
expect(() => act(() => {}).then(() => {})).toErrorDev(
366377
[
@@ -372,6 +383,7 @@ function runActTests(label, render, unmount, rerender) {
372383
});
373384

374385
describe('asynchronous tests', () => {
386+
// @gate __DEV__
375387
it('works with timeouts', async () => {
376388
function App() {
377389
const [ctr, setCtr] = React.useState(0);
@@ -396,6 +408,7 @@ function runActTests(label, render, unmount, rerender) {
396408
expect(container.innerHTML).toBe('1');
397409
});
398410

411+
// @gate __DEV__
399412
it('flushes microtasks before exiting', async () => {
400413
function App() {
401414
const [ctr, setCtr] = React.useState(0);
@@ -418,6 +431,7 @@ function runActTests(label, render, unmount, rerender) {
418431
expect(container.innerHTML).toEqual('1');
419432
});
420433

434+
// @gate __DEV__
421435
it('warns if you do not await an act call', async () => {
422436
spyOnDevAndProd(console, 'error');
423437
act(async () => {});
@@ -431,6 +445,7 @@ function runActTests(label, render, unmount, rerender) {
431445
}
432446
});
433447

448+
// @gate __DEV__
434449
it('warns if you try to interleave multiple act calls', async () => {
435450
spyOnDevAndProd(console, 'error');
436451
// let's try to cheat and spin off a 'thread' with an act call
@@ -450,6 +465,7 @@ function runActTests(label, render, unmount, rerender) {
450465
}
451466
});
452467

468+
// @gate __DEV__
453469
it('async commits and effects are guaranteed to be flushed', async () => {
454470
function App() {
455471
const [state, setState] = React.useState(0);
@@ -475,6 +491,7 @@ function runActTests(label, render, unmount, rerender) {
475491
expect(container.innerHTML).toBe('1');
476492
});
477493

494+
// @gate __DEV__
478495
it('can handle cascading promises', async () => {
479496
// this component triggers an effect, that waits a tick,
480497
// then sets state. repeats this 5 times.
@@ -501,6 +518,7 @@ function runActTests(label, render, unmount, rerender) {
501518
});
502519

503520
describe('error propagation', () => {
521+
// @gate __DEV__
504522
it('propagates errors - sync', () => {
505523
let err;
506524
try {
@@ -515,6 +533,7 @@ function runActTests(label, render, unmount, rerender) {
515533
}
516534
});
517535

536+
// @gate __DEV__
518537
it('should propagate errors from effects - sync', () => {
519538
function App() {
520539
React.useEffect(() => {
@@ -536,6 +555,7 @@ function runActTests(label, render, unmount, rerender) {
536555
}
537556
});
538557

558+
// @gate __DEV__
539559
it('propagates errors - async', async () => {
540560
let err;
541561
try {
@@ -551,6 +571,7 @@ function runActTests(label, render, unmount, rerender) {
551571
}
552572
});
553573

574+
// @gate __DEV__
554575
it('should cleanup after errors - sync', () => {
555576
function App() {
556577
React.useEffect(() => {
@@ -576,6 +597,7 @@ function runActTests(label, render, unmount, rerender) {
576597
}
577598
});
578599

600+
// @gate __DEV__
579601
it('should cleanup after errors - async', async () => {
580602
function App() {
581603
async function somethingAsync() {
@@ -611,6 +633,7 @@ function runActTests(label, render, unmount, rerender) {
611633
if (__DEV__ && __EXPERIMENTAL__) {
612634
// todo - remove __DEV__ check once we start using testing builds
613635

636+
// @gate __DEV__
614637
it('triggers fallbacks if available', async () => {
615638
if (label !== 'legacy mode') {
616639
// FIXME: Support for Concurrent Root intentionally removed
@@ -691,25 +714,12 @@ function runActTests(label, render, unmount, rerender) {
691714
});
692715
}
693716
});
694-
describe('warn in prod mode', () => {
717+
describe('throw in prod mode', () => {
718+
// @gate !__DEV__
695719
it('warns if you try to use act() in prod mode', () => {
696-
const spy = spyOnDevAndProd(console, 'error');
697-
698-
act(() => {});
699-
700-
if (!__DEV__) {
701-
expect(console.error).toHaveBeenCalledTimes(1);
702-
expect(console.error.calls.argsFor(0)[0]).toContain(
703-
'act(...) is not supported in production builds of React',
704-
);
705-
} else {
706-
expect(console.error).toHaveBeenCalledTimes(0);
707-
}
708-
709-
spy.calls.reset();
710-
// does not warn twice
711-
act(() => {});
712-
expect(console.error).toHaveBeenCalledTimes(0);
720+
expect(() => act(() => {})).toThrow(
721+
'act(...) is not supported in production builds of React',
722+
);
713723
});
714724
});
715725
});

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ afterEach(() => {
4747
document.body.removeChild(container);
4848
});
4949

50+
// @gate __DEV__
5051
it('can use act to flush effects', () => {
5152
function App() {
5253
React.useEffect(() => {
@@ -62,6 +63,7 @@ it('can use act to flush effects', () => {
6263
expect(clearYields()).toEqual([100]);
6364
});
6465

66+
// @gate __DEV__
6567
it('flushes effects on every call', () => {
6668
function App() {
6769
const [ctr, setCtr] = React.useState(0);
@@ -100,6 +102,7 @@ it('flushes effects on every call', () => {
100102
expect(button.innerHTML).toEqual('5');
101103
});
102104

105+
// @gate __DEV__
103106
it("should keep flushing effects until they're done", () => {
104107
function App() {
105108
const [ctr, setCtr] = React.useState(0);
@@ -118,6 +121,7 @@ it("should keep flushing effects until they're done", () => {
118121
expect(container.innerHTML).toEqual('5');
119122
});
120123

124+
// @gate __DEV__
121125
it('should flush effects only on exiting the outermost act', () => {
122126
function App() {
123127
React.useEffect(() => {
@@ -138,6 +142,7 @@ it('should flush effects only on exiting the outermost act', () => {
138142
expect(clearYields()).toEqual([0]);
139143
});
140144

145+
// @gate __DEV__
141146
it('can handle cascading promises', async () => {
142147
// this component triggers an effect, that waits a tick,
143148
// then sets state. repeats this 5 times.

packages/react-dom/src/test-utils/ReactTestUtilsPublicAct.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as ReactDOM from 'react-dom';
1313
import ReactSharedInternals from 'shared/ReactSharedInternals';
1414
import enqueueTask from 'shared/enqueueTask';
1515
import * as Scheduler from 'scheduler';
16+
import invariant from 'shared/invariant';
1617

1718
// Keep in sync with ReactDOM.js, and ReactTestUtils.js:
1819
const EventInternals =
@@ -71,17 +72,13 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
7172
// so we can tell if any async act() calls try to run in parallel.
7273

7374
let actingUpdatesScopeDepth = 0;
74-
let didWarnAboutUsingActInProd = false;
7575

7676
export function act(callback: () => Thenable<mixed>): Thenable<void> {
7777
if (!__DEV__) {
78-
if (didWarnAboutUsingActInProd === false) {
79-
didWarnAboutUsingActInProd = true;
80-
// eslint-disable-next-line react-internal/no-production-logging
81-
console.error(
82-
'act(...) is not supported in production builds of React, and might not behave as expected.',
83-
);
84-
}
78+
invariant(
79+
false,
80+
'act(...) is not supported in production builds of React.',
81+
);
8582
}
8683
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
8784
actingUpdatesScopeDepth++;

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2985,17 +2985,13 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
29852985
// so we can tell if any async act() calls try to run in parallel.
29862986

29872987
let actingUpdatesScopeDepth = 0;
2988-
let didWarnAboutUsingActInProd = false;
29892988

29902989
export function act(callback: () => Thenable<mixed>): Thenable<void> {
29912990
if (!__DEV__) {
2992-
if (didWarnAboutUsingActInProd === false) {
2993-
didWarnAboutUsingActInProd = true;
2994-
// eslint-disable-next-line react-internal/no-production-logging
2995-
console.error(
2996-
'act(...) is not supported in production builds of React, and might not behave as expected.',
2997-
);
2998-
}
2991+
invariant(
2992+
false,
2993+
'act(...) is not supported in production builds of React.',
2994+
);
29992995
}
30002996

30012997
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2985,17 +2985,13 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
29852985
// so we can tell if any async act() calls try to run in parallel.
29862986

29872987
let actingUpdatesScopeDepth = 0;
2988-
let didWarnAboutUsingActInProd = false;
29892988

29902989
export function act(callback: () => Thenable<mixed>): Thenable<void> {
29912990
if (!__DEV__) {
2992-
if (didWarnAboutUsingActInProd === false) {
2993-
didWarnAboutUsingActInProd = true;
2994-
// eslint-disable-next-line react-internal/no-production-logging
2995-
console.error(
2996-
'act(...) is not supported in production builds of React, and might not behave as expected.',
2997-
);
2998-
}
2991+
invariant(
2992+
false,
2993+
'act(...) is not supported in production builds of React.',
2994+
);
29992995
}
30002996

30012997
const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;

packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('ReactFiberHostContext', () => {
2626
.DefaultEventPriority;
2727
});
2828

29+
// @gate __DEV__
2930
it('works with null host context', async () => {
3031
let creates = 0;
3132
const Renderer = ReactFiberReconciler({
@@ -83,6 +84,7 @@ describe('ReactFiberHostContext', () => {
8384
expect(creates).toBe(2);
8485
});
8586

87+
// @gate __DEV__
8688
it('should send the context to prepareForCommit and resetAfterCommit', () => {
8789
const rootContext = {};
8890
const Renderer = ReactFiberReconciler({

0 commit comments

Comments
 (0)