@samitouri / QOS-React-2 / commits / ba31ad40a9

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');