feat(StrictMode): Double-invoke render for every component (#18430)
* feat(StrictMode): Double-invoke render for every component * fix: Mark ReactTestRendererAsync as internal
Sebastian Silbermann committed
Mar 30, 2020 at 00:13 UTC
ba31ad40a9c6495e0d42def270178a7a74990c27
12 files changed
+76
-73
packages/react-art/src/__tests__/ReactART-test.js
+4
-2
@@ -391,7 +391,7 @@ describe('ReactART', () => {
391
</CurrentRendererContext.Provider>,
392
);
393
394
- expect(Scheduler).toFlushAndYieldThrough(['A']);
394
+ expect(Scheduler).toFlushAndYieldThrough(__DEV__ ? ['A', 'A'] : ['A']);
395
396
ReactDOM.render(
397
<Surface>
@@ -406,7 +406,9 @@ describe('ReactART', () => {
406
expect(ops).toEqual([null, 'ART']);
407
408
ops = [];
409
- expect(Scheduler).toFlushAndYield(['B', 'C']);
409
+ expect(Scheduler).toFlushAndYield(
410
+ __DEV__ ? ['B', 'B', 'C', 'C'] : ['B', 'C'],
411
+ );
412
413
expect(ops).toEqual(['Test']);
414
});
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+3
-1
@@ -639,7 +639,9 @@ describe('ReactDOMFiberAsync', () => {
639
expect(container.textContent).toEqual('');
640
641
// Everything should render immediately in the next event
642
- expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
642
+ expect(Scheduler).toFlushExpired(
643
+ __DEV__ ? ['A', 'A', 'B', 'B', 'C', 'C'] : ['A', 'B', 'C'],
644
+ );
645
expect(container.textContent).toEqual('ABC');
646
});
647
});
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+3
-1
@@ -607,7 +607,9 @@ function runActTests(label, render, unmount, rerender) {
607
},
608
);
609
610
- expect(Component).toHaveBeenCalledTimes(4);
610
+ expect(Component).toHaveBeenCalledTimes(
611
+ label === 'legacy mode' ? 4 : 8,
612
+ );
613
unmount(secondContainer);
614
});
615
}
packages/react-dom/src/__tests__/ReactUpdates-test.js
+1
@@ -1333,6 +1333,7 @@ describe('ReactUpdates', () => {
1333
'Foo',
1334
'Foo',
1335
'Baz',
1336
+ 'Baz',
1337
'Foo#effect',
1338
]);
1339
} else {
packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js
+4
-2
@@ -813,7 +813,7 @@ describe('DOMEventResponderSystem', () => {
813
814
let root = ReactDOM.createRoot(container);
815
root.render(<Test counter={0} />);
816
- expect(Scheduler).toFlushAndYield(['Test']);
816
+ expect(Scheduler).toFlushAndYield(__DEV__ ? ['Test', 'Test'] : ['Test']);
817
818
// Click the button
819
dispatchClickEvent(ref.current);
@@ -825,7 +825,9 @@ describe('DOMEventResponderSystem', () => {
825
// Increase counter
826
root.render(<Test counter={1} />);
827
// Yield before committing
828
- expect(Scheduler).toFlushAndYieldThrough(['Test']);
828
+ expect(Scheduler).toFlushAndYieldThrough(
829
+ __DEV__ ? ['Test', 'Test'] : ['Test'],
830
+ );
831
832
// Click the button again
833
dispatchClickEvent(ref.current);
packages/react-reconciler/src/ReactFiberBeginWork.js
+32
-44
@@ -321,17 +321,14 @@ function updateForwardRef(
321
debugRenderPhaseSideEffectsForStrictMode &&
322
workInProgress.mode & StrictMode
323
) {
324
- // Only double-render components with Hooks
325
- if (workInProgress.memoizedState !== null) {
326
- nextChildren = renderWithHooks(
327
- current,
328
- workInProgress,
329
- render,
330
- nextProps,
331
- ref,
332
- renderExpirationTime,
333
- );
334
- }
324
+ nextChildren = renderWithHooks(
325
+ current,
326
+ workInProgress,
327
+ render,
328
+ nextProps,
329
+ ref,
330
+ renderExpirationTime,
331
+ );
332
}
333
setIsRendering(false);
334
} else {
@@ -662,17 +659,14 @@ function updateFunctionComponent(
659
debugRenderPhaseSideEffectsForStrictMode &&
660
workInProgress.mode & StrictMode
661
) {
665
- // Only double-render components with Hooks
666
- if (workInProgress.memoizedState !== null) {
667
- nextChildren = renderWithHooks(
668
- current,
669
- workInProgress,
670
- Component,
671
- nextProps,
672
- context,
673
- renderExpirationTime,
674
- );
675
- }
662
+ nextChildren = renderWithHooks(
663
+ current,
664
+ workInProgress,
665
+ Component,
666
+ nextProps,
667
+ context,
668
+ renderExpirationTime,
669
+ );
670
}
671
setIsRendering(false);
672
} else {
@@ -738,17 +732,14 @@ function updateBlock<Props, Data>(
732
debugRenderPhaseSideEffectsForStrictMode &&
733
workInProgress.mode & StrictMode
734
) {
741
- // Only double-render components with Hooks
742
- if (workInProgress.memoizedState !== null) {
743
- nextChildren = renderWithHooks(
744
- current,
745
- workInProgress,
746
- render,
747
- nextProps,
748
- data,
749
- renderExpirationTime,
750
- );
751
- }
735
+ nextChildren = renderWithHooks(
736
+ current,
737
+ workInProgress,
738
+ render,
739
+ nextProps,
740
+ data,
741
+ renderExpirationTime,
742
+ );
743
}
744
setIsRendering(false);
745
} else {
@@ -1471,17 +1462,14 @@ function mountIndeterminateComponent(
1462
debugRenderPhaseSideEffectsForStrictMode &&
1463
workInProgress.mode & StrictMode
1464
) {
1474
- // Only double-render components with Hooks
1475
- if (workInProgress.memoizedState !== null) {
1476
- value = renderWithHooks(
1477
- null,
1478
- workInProgress,
1479
- Component,
1480
- props,
1481
- context,
1482
- renderExpirationTime,
1483
- );
1484
- }
1465
+ value = renderWithHooks(
1466
+ null,
1467
+ workInProgress,
1468
+ Component,
1469
+ props,
1470
+ context,
1471
+ renderExpirationTime,
1472
+ );
1473
}
1474
}
1475
reconcileChildren(null, workInProgress, value, renderExpirationTime);
packages/react-reconciler/src/__tests__/ErrorBoundaryReconciliation-test.internal.js
+1
@@ -12,6 +12,7 @@ describe('ErrorBoundaryReconciliation', () => {
12
jest.resetModules();
13
14
ReactFeatureFlags = require('shared/ReactFeatureFlags');
15
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
16
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
17
ReactTestRenderer = require('react-test-renderer');
18
React = require('react');
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+6
-6
@@ -1300,14 +1300,14 @@ describe('ReactHooks', () => {
1300
<NoHooks />
1301
</StrictMode>,
1302
);
1303
- expect(renderCount).toBe(1);
1303
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1304
renderCount = 0;
1305
renderer.update(
1306
<StrictMode>
1307
<NoHooks />
1308
</StrictMode>,
1309
);
1310
- expect(renderCount).toBe(1);
1310
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1311
1312
renderCount = 0;
1313
renderer.update(<FwdRef />);
@@ -1321,14 +1321,14 @@ describe('ReactHooks', () => {
1321
<FwdRef />
1322
</StrictMode>,
1323
);
1324
- expect(renderCount).toBe(1);
1324
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1325
renderCount = 0;
1326
renderer.update(
1327
<StrictMode>
1328
<FwdRef />
1329
</StrictMode>,
1330
);
1331
- expect(renderCount).toBe(1);
1331
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1332
1333
renderCount = 0;
1334
renderer.update(<Memo arg={1} />);
@@ -1342,14 +1342,14 @@ describe('ReactHooks', () => {
1342
<Memo arg={1} />
1343
</StrictMode>,
1344
);
1345
- expect(renderCount).toBe(1);
1345
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1346
renderCount = 0;
1347
renderer.update(
1348
<StrictMode>
1349
<Memo arg={2} />
1350
</StrictMode>,
1351
);
1352
- expect(renderCount).toBe(1);
1352
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
1353
1354
renderCount = 0;
1355
expect(() => renderer.update(<Factory />)).toErrorDev(
packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js
+3
-1
@@ -1103,7 +1103,9 @@ describe('ReactNewContext', () => {
1103
1104
// Render the provider again using a different renderer
1105
ReactNoop.render(<App value={1} />);
1106
- expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
1106
+ expect(Scheduler).toFlushAndYield(
1107
+ __DEV__ ? ['Foo', 'Foo', 'Foo', 'Foo'] : ['Foo', 'Foo'],
1108
+ );
1109
1110
if (__DEV__) {
1111
expect(console.error.calls.argsFor(0)[0]).toContain(
packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.internal.js
renamed
+3
@@ -11,12 +11,15 @@
11
'use strict';
12
13
let React;
14
+let ReactFeatureFlags;
15
let ReactTestRenderer;
16
let Scheduler;
17
18
describe('ReactTestRendererAsync', () => {
19
beforeEach(() => {
20
jest.resetModules();
21
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
22
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
23
React = require('react');
24
ReactTestRenderer = require('react-test-renderer');
25
Scheduler = require('scheduler');
packages/react/src/__tests__/ReactProfilerDevToolsIntegration-test.internal.js
+2
-2
@@ -188,7 +188,7 @@ describe('ReactProfiler DevTools integration', () => {
188
189
// Commit something
190
root.update(<Text text="A" />);
191
- expect(Scheduler).toFlushAndYield(['A']);
191
+ expect(Scheduler).toFlushAndYield(__DEV__ ? ['A', 'A'] : ['A']);
192
expect(root).toMatchRenderedOutput('A');
193
194
// Advance time by many seconds, larger than the default expiration time
@@ -200,7 +200,7 @@ describe('ReactProfiler DevTools integration', () => {
200
// Update B should not instantly expire.
201
expect(Scheduler).toFlushExpired([]);
202
203
- expect(Scheduler).toFlushAndYield(['B']);
203
+ expect(Scheduler).toFlushAndYield(__DEV__ ? ['B', 'B'] : ['B']);
204
expect(root).toMatchRenderedOutput('B');
205
});
206
});
packages/react/src/__tests__/forwardRef-test.js
+14
-14
@@ -260,11 +260,11 @@ describe('forwardRef', () => {
260
261
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
262
expect(Scheduler).toFlushWithoutYielding();
263
- expect(renderCount).toBe(1);
263
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
264
265
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
266
expect(Scheduler).toFlushWithoutYielding();
267
- expect(renderCount).toBe(2);
267
+ expect(renderCount).toBe(__DEV__ ? 4 : 2);
268
});
269
270
it('should bailout if forwardRef is wrapped in memo', () => {
@@ -283,13 +283,13 @@ describe('forwardRef', () => {
283
284
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
285
expect(Scheduler).toFlushWithoutYielding();
286
- expect(renderCount).toBe(1);
286
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
287
288
expect(ref.current.type).toBe('div');
289
290
ReactNoop.render(<RefForwardingComponent ref={ref} optional="foo" />);
291
expect(Scheduler).toFlushWithoutYielding();
292
- expect(renderCount).toBe(1);
292
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
293
294
const differentRef = React.createRef();
295
@@ -297,14 +297,14 @@ describe('forwardRef', () => {
297
<RefForwardingComponent ref={differentRef} optional="foo" />,
298
);
299
expect(Scheduler).toFlushWithoutYielding();
300
- expect(renderCount).toBe(2);
300
+ expect(renderCount).toBe(__DEV__ ? 4 : 2);
301
302
expect(ref.current).toBe(null);
303
expect(differentRef.current.type).toBe('div');
304
305
ReactNoop.render(<RefForwardingComponent ref={ref} optional="bar" />);
306
expect(Scheduler).toFlushWithoutYielding();
307
- expect(renderCount).toBe(3);
307
+ expect(renderCount).toBe(__DEV__ ? 6 : 3);
308
});
309
310
it('should custom memo comparisons to compose', () => {
@@ -324,19 +324,19 @@ describe('forwardRef', () => {
324
325
ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="0" c="1" />);
326
expect(Scheduler).toFlushWithoutYielding();
327
- expect(renderCount).toBe(1);
327
+ expect(renderCount).toBe(__DEV__ ? 2 : 1);
328
329
expect(ref.current.type).toBe('div');
330
331
// Changing either a or b rerenders
332
ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="1" c="1" />);
333
expect(Scheduler).toFlushWithoutYielding();
334
- expect(renderCount).toBe(2);
334
+ expect(renderCount).toBe(__DEV__ ? 4 : 2);
335
336
// Changing c doesn't rerender
337
ReactNoop.render(<RefForwardingComponent ref={ref} a="0" b="1" c="2" />);
338
expect(Scheduler).toFlushWithoutYielding();
339
- expect(renderCount).toBe(2);
339
+ expect(renderCount).toBe(__DEV__ ? 4 : 2);
340
341
const ComposedMemo = React.memo(
342
RefForwardingComponent,
@@ -345,29 +345,29 @@ describe('forwardRef', () => {
345
346
ReactNoop.render(<ComposedMemo ref={ref} a="0" b="0" c="0" />);
347
expect(Scheduler).toFlushWithoutYielding();
348
- expect(renderCount).toBe(3);
348
+ expect(renderCount).toBe(__DEV__ ? 6 : 3);
349
350
// Changing just b no longer updates
351
ReactNoop.render(<ComposedMemo ref={ref} a="0" b="1" c="0" />);
352
expect(Scheduler).toFlushWithoutYielding();
353
- expect(renderCount).toBe(3);
353
+ expect(renderCount).toBe(__DEV__ ? 6 : 3);
354
355
// Changing just a and c updates
356
ReactNoop.render(<ComposedMemo ref={ref} a="2" b="2" c="2" />);
357
expect(Scheduler).toFlushWithoutYielding();
358
- expect(renderCount).toBe(4);
358
+ expect(renderCount).toBe(__DEV__ ? 8 : 4);
359
360
// Changing just c does not update
361
ReactNoop.render(<ComposedMemo ref={ref} a="2" b="2" c="3" />);
362
expect(Scheduler).toFlushWithoutYielding();
363
- expect(renderCount).toBe(4);
363
+ expect(renderCount).toBe(__DEV__ ? 8 : 4);
364
365
// Changing ref still rerenders
366
const differentRef = React.createRef();
367
368
ReactNoop.render(<ComposedMemo ref={differentRef} a="2" b="2" c="3" />);
369
expect(Scheduler).toFlushWithoutYielding();
370
- expect(renderCount).toBe(5);
370
+ expect(renderCount).toBe(__DEV__ ? 10 : 5);
371
372
expect(ref.current).toBe(null);
373
expect(differentRef.current.type).toBe('div');