main
js 1,297 lines 36.5 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and 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 'use strict';
11
12 let React;
13 let ReactDOM;
14 let ReactDOMClient;
15 let ReactDOMServer;
16 let PropTypes;
17 let act;
18 let useMemo;
19 let useState;
20 let useReducer;
21 let assertConsoleErrorDev;
22 let assertConsoleWarnDev;
23
24 describe('ReactStrictMode', () => {
25 beforeEach(() => {
26 jest.resetModules();
27 React = require('react');
28 ReactDOM = require('react-dom');
29 ReactDOMClient = require('react-dom/client');
30 ReactDOMServer = require('react-dom/server');
31 ({
32 act,
33 assertConsoleErrorDev,
34 assertConsoleWarnDev,
35 } = require('internal-test-utils'));
36 useMemo = React.useMemo;
37 useState = React.useState;
38 useReducer = React.useReducer;
39 });
40
41 it('should appear in the client component stack', async () => {
42 function Foo() {
43 return <div ariaTypo="" />;
44 }
45
46 const container = document.createElement('div');
47 const root = ReactDOMClient.createRoot(container);
48 await act(() => {
49 root.render(
50 <React.StrictMode>
51 <Foo />
52 </React.StrictMode>,
53 );
54 });
55 assertConsoleErrorDev([
56 'Invalid ARIA attribute `ariaTypo`. ' +
57 'ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
58 ' in div (at **)\n' +
59 ' in Foo (at **)',
60 ]);
61 });
62
63 it('should appear in the SSR component stack', () => {
64 function Foo() {
65 return <div ariaTypo="" />;
66 }
67
68 ReactDOMServer.renderToString(
69 <React.StrictMode>
70 <Foo />
71 </React.StrictMode>,
72 );
73 assertConsoleErrorDev([
74 'Invalid ARIA attribute `ariaTypo`. ' +
75 'ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
76 ' in div (at **)\n' +
77 ' in Foo (at **)',
78 ]);
79 });
80
81 // @gate __DEV__
82 // @gate !disableLegacyMode
83 it('should invoke only precommit lifecycle methods twice in legacy roots', async () => {
84 let log = [];
85 let shouldComponentUpdate = false;
86 class ClassComponent extends React.Component {
87 state = {};
88 static getDerivedStateFromProps() {
89 log.push('getDerivedStateFromProps');
90 return null;
91 }
92 constructor(props) {
93 super(props);
94 log.push('constructor');
95 }
96 componentDidMount() {
97 log.push('componentDidMount');
98 }
99 componentDidUpdate() {
100 log.push('componentDidUpdate');
101 }
102 componentWillUnmount() {
103 log.push('componentWillUnmount');
104 }
105 shouldComponentUpdate() {
106 log.push('shouldComponentUpdate');
107 return shouldComponentUpdate;
108 }
109 render() {
110 log.push('render');
111 return null;
112 }
113 }
114
115 const container = document.createElement('div');
116 ReactDOM.render(
117 <React.StrictMode>
118 <ClassComponent />
119 </React.StrictMode>,
120 container,
121 );
122
123 expect(log).toEqual([
124 'constructor',
125 'constructor',
126 'getDerivedStateFromProps',
127 'getDerivedStateFromProps',
128 'render',
129 'render',
130 'componentDidMount',
131 ]);
132
133 log = [];
134 shouldComponentUpdate = true;
135
136 ReactDOM.render(
137 <React.StrictMode>
138 <ClassComponent />
139 </React.StrictMode>,
140 container,
141 );
142 expect(log).toEqual([
143 'getDerivedStateFromProps',
144 'getDerivedStateFromProps',
145 'shouldComponentUpdate',
146 'shouldComponentUpdate',
147 'render',
148 'render',
149 'componentDidUpdate',
150 ]);
151
152 log = [];
153 shouldComponentUpdate = false;
154
155 ReactDOM.render(
156 <React.StrictMode>
157 <ClassComponent />
158 </React.StrictMode>,
159 container,
160 );
161
162 expect(log).toEqual([
163 'getDerivedStateFromProps',
164 'getDerivedStateFromProps',
165 'shouldComponentUpdate',
166 'shouldComponentUpdate',
167 ]);
168 });
169
170 it('should invoke setState callbacks twice', async () => {
171 let instance;
172 class ClassComponent extends React.Component {
173 state = {
174 count: 1,
175 };
176 render() {
177 instance = this;
178 return null;
179 }
180 }
181
182 let setStateCount = 0;
183
184 const container = document.createElement('div');
185 const root = ReactDOMClient.createRoot(container);
186 await act(() => {
187 root.render(
188 <React.StrictMode>
189 <ClassComponent />
190 </React.StrictMode>,
191 );
192 });
193 await act(() => {
194 instance.setState(state => {
195 setStateCount++;
196 return {
197 count: state.count + 1,
198 };
199 });
200 });
201
202 // Callback should be invoked twice in DEV
203 expect(setStateCount).toBe(__DEV__ ? 2 : 1);
204 // But each time `state` should be the previous value
205 expect(instance.state.count).toBe(2);
206 });
207
208 // @gate __DEV__
209 it('double invokes useState and useReducer initializers functions', async () => {
210 const log = [];
211
212 function App() {
213 React.useState(() => {
214 log.push('Compute initial state count: 1');
215 return 1;
216 });
217 React.useReducer(
218 s => s,
219 2,
220 s => {
221 log.push('Compute initial reducer count: 2');
222 return s;
223 },
224 );
225
226 return 3;
227 }
228
229 const container = document.createElement('div');
230 const root = ReactDOMClient.createRoot(container);
231 await act(() => {
232 root.render(
233 <React.StrictMode>
234 <App />
235 </React.StrictMode>,
236 );
237 });
238 expect(container.textContent).toBe('3');
239
240 expect(log).toEqual([
241 'Compute initial state count: 1',
242 'Compute initial state count: 1',
243 'Compute initial reducer count: 2',
244 'Compute initial reducer count: 2',
245 ]);
246 });
247
248 // @gate !disableLegacyMode
249 it('should invoke only precommit lifecycle methods twice in DEV legacy roots', async () => {
250 const {StrictMode} = React;
251
252 let log = [];
253 let shouldComponentUpdate = false;
254
255 function Root() {
256 return (
257 <StrictMode>
258 <ClassComponent />
259 </StrictMode>
260 );
261 }
262
263 class ClassComponent extends React.Component {
264 state = {};
265 static getDerivedStateFromProps() {
266 log.push('getDerivedStateFromProps');
267 return null;
268 }
269 constructor(props) {
270 super(props);
271 log.push('constructor');
272 }
273 componentDidMount() {
274 log.push('componentDidMount');
275 }
276 componentDidUpdate() {
277 log.push('componentDidUpdate');
278 }
279 componentWillUnmount() {
280 log.push('componentWillUnmount');
281 }
282 shouldComponentUpdate() {
283 log.push('shouldComponentUpdate');
284 return shouldComponentUpdate;
285 }
286 render() {
287 log.push('render');
288 return null;
289 }
290 }
291
292 const container = document.createElement('div');
293 ReactDOM.render(<Root />, container);
294
295 if (__DEV__) {
296 expect(log).toEqual([
297 'constructor',
298 'constructor',
299 'getDerivedStateFromProps',
300 'getDerivedStateFromProps',
301 'render',
302 'render',
303 'componentDidMount',
304 ]);
305 } else {
306 expect(log).toEqual([
307 'constructor',
308 'getDerivedStateFromProps',
309 'render',
310 'componentDidMount',
311 ]);
312 }
313
314 log = [];
315 shouldComponentUpdate = true;
316
317 ReactDOM.render(<Root />, container);
318 if (__DEV__) {
319 expect(log).toEqual([
320 'getDerivedStateFromProps',
321 'getDerivedStateFromProps',
322 'shouldComponentUpdate',
323 'shouldComponentUpdate',
324 'render',
325 'render',
326 'componentDidUpdate',
327 ]);
328 } else {
329 expect(log).toEqual([
330 'getDerivedStateFromProps',
331 'shouldComponentUpdate',
332 'render',
333 'componentDidUpdate',
334 ]);
335 }
336
337 log = [];
338 shouldComponentUpdate = false;
339
340 ReactDOM.render(<Root />, container);
341 if (__DEV__) {
342 expect(log).toEqual([
343 'getDerivedStateFromProps',
344 'getDerivedStateFromProps',
345 'shouldComponentUpdate',
346 'shouldComponentUpdate',
347 ]);
348 } else {
349 expect(log).toEqual([
350 'getDerivedStateFromProps',
351 'shouldComponentUpdate',
352 ]);
353 }
354 });
355
356 it('should invoke setState callbacks twice in DEV', async () => {
357 const {StrictMode} = React;
358
359 let instance;
360 class ClassComponent extends React.Component {
361 state = {
362 count: 1,
363 };
364 render() {
365 instance = this;
366 return null;
367 }
368 }
369
370 let setStateCount = 0;
371
372 const container = document.createElement('div');
373 const root = ReactDOMClient.createRoot(container);
374 await act(() => {
375 root.render(
376 <StrictMode>
377 <ClassComponent />
378 </StrictMode>,
379 );
380 });
381 await act(() => {
382 instance.setState(state => {
383 setStateCount++;
384 return {
385 count: state.count + 1,
386 };
387 });
388 });
389
390 // Callback should be invoked twice (in DEV)
391 expect(setStateCount).toBe(__DEV__ ? 2 : 1);
392 // But each time `state` should be the previous value
393 expect(instance.state.count).toBe(2);
394 });
395
396 // @gate __DEV__
397 it('double invokes useMemo functions', async () => {
398 let log = [];
399
400 function Uppercased({text}) {
401 return useMemo(() => {
402 const uppercased = text.toUpperCase();
403 log.push('Compute toUpperCase: ' + uppercased);
404 return uppercased;
405 }, [text]);
406 }
407
408 const container = document.createElement('div');
409 const root = ReactDOMClient.createRoot(container);
410
411 // Mount
412 await act(() => {
413 root.render(
414 <React.StrictMode>
415 <Uppercased text="hello" />
416 </React.StrictMode>,
417 );
418 });
419 expect(container.textContent).toBe('HELLO');
420 expect(log).toEqual([
421 'Compute toUpperCase: HELLO',
422 'Compute toUpperCase: HELLO',
423 ]);
424
425 log = [];
426
427 // Update
428 await act(() => {
429 root.render(
430 <React.StrictMode>
431 <Uppercased text="goodbye" />
432 </React.StrictMode>,
433 );
434 });
435 expect(container.textContent).toBe('GOODBYE');
436 expect(log).toEqual([
437 'Compute toUpperCase: GOODBYE',
438 'Compute toUpperCase: GOODBYE',
439 ]);
440 });
441
442 // @gate __DEV__
443 it('double invokes useMemo functions with first result', async () => {
444 let log = [];
445 function Uppercased({text}) {
446 const memoizedResult = useMemo(() => {
447 const uppercased = text.toUpperCase();
448 log.push('Compute toUpperCase: ' + uppercased);
449 return {uppercased};
450 }, [text]);
451
452 // Push this to the log so we can check whether the same memoized result
453 // it returned during both invocations.
454 log.push(memoizedResult);
455
456 return memoizedResult.uppercased;
457 }
458
459 const container = document.createElement('div');
460 const root = ReactDOMClient.createRoot(container);
461
462 // Mount
463 await act(() => {
464 root.render(
465 <React.StrictMode>
466 <Uppercased text="hello" />
467 </React.StrictMode>,
468 );
469 });
470 expect(container.textContent).toBe('HELLO');
471 expect(log).toEqual([
472 'Compute toUpperCase: HELLO',
473 'Compute toUpperCase: HELLO',
474 {uppercased: 'HELLO'},
475 {uppercased: 'HELLO'},
476 ]);
477
478 // Even though the memoized function is invoked twice, the same object
479 // is returned both times.
480 expect(log[2]).toBe(log[3]);
481
482 log = [];
483
484 // Update
485 await act(() => {
486 root.render(
487 <React.StrictMode>
488 <Uppercased text="goodbye" />
489 </React.StrictMode>,
490 );
491 });
492 expect(container.textContent).toBe('GOODBYE');
493 expect(log).toEqual([
494 'Compute toUpperCase: GOODBYE',
495 'Compute toUpperCase: GOODBYE',
496 {uppercased: 'GOODBYE'},
497 {uppercased: 'GOODBYE'},
498 ]);
499
500 // Even though the memoized function is invoked twice, the same object
501 // is returned both times.
502 expect(log[2]).toBe(log[3]);
503 });
504
505 // @gate __DEV__
506 it('double invokes setState updater functions', async () => {
507 const log = [];
508
509 let setCount;
510 function App() {
511 const [count, _setCount] = useState(0);
512 setCount = _setCount;
513 return count;
514 }
515
516 const container = document.createElement('div');
517 const root = ReactDOMClient.createRoot(container);
518
519 await act(() => {
520 root.render(
521 <React.StrictMode>
522 <App />
523 </React.StrictMode>,
524 );
525 });
526 expect(container.textContent).toBe('0');
527
528 await act(() => {
529 setCount(() => {
530 log.push('Compute count: 1');
531 return 1;
532 });
533 });
534 expect(container.textContent).toBe('1');
535 expect(log).toEqual(['Compute count: 1', 'Compute count: 1']);
536 });
537
538 // @gate __DEV__
539 it('double invokes reducer functions', async () => {
540 const log = [];
541
542 function reducer(prevState, action) {
543 log.push('Compute new state: ' + action);
544 return action;
545 }
546
547 let dispatch;
548 function App() {
549 const [count, _dispatch] = useReducer(reducer, 0);
550 dispatch = _dispatch;
551 return count;
552 }
553
554 const container = document.createElement('div');
555 const root = ReactDOMClient.createRoot(container);
556
557 await act(() => {
558 root.render(
559 <React.StrictMode>
560 <App />
561 </React.StrictMode>,
562 );
563 });
564 expect(container.textContent).toBe('0');
565
566 await act(() => {
567 dispatch(1);
568 });
569 expect(container.textContent).toBe('1');
570 expect(log).toEqual(['Compute new state: 1', 'Compute new state: 1']);
571 });
572 });
573
574 describe('Concurrent Mode', () => {
575 beforeEach(() => {
576 jest.resetModules();
577
578 React = require('react');
579 ReactDOMClient = require('react-dom/client');
580 act = require('internal-test-utils').act;
581 });
582
583 it('should warn about unsafe legacy lifecycle methods anywhere in a StrictMode tree', async () => {
584 function StrictRoot() {
585 return (
586 <React.StrictMode>
587 <App />
588 </React.StrictMode>
589 );
590 }
591 class App extends React.Component {
592 UNSAFE_componentWillMount() {}
593 UNSAFE_componentWillUpdate() {}
594 render() {
595 return (
596 <div>
597 <Wrapper>
598 <Foo />
599 </Wrapper>
600 <div>
601 <Bar />
602 <Foo />
603 </div>
604 </div>
605 );
606 }
607 }
608 function Wrapper({children}) {
609 return <div>{children}</div>;
610 }
611 class Foo extends React.Component {
612 UNSAFE_componentWillReceiveProps() {}
613 render() {
614 return null;
615 }
616 }
617 class Bar extends React.Component {
618 UNSAFE_componentWillReceiveProps() {}
619 render() {
620 return null;
621 }
622 }
623
624 const container = document.createElement('div');
625 const root = ReactDOMClient.createRoot(container);
626 await act(() => root.render(<StrictRoot />));
627 assertConsoleErrorDev([
628 `Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
629
630 * Move code with side effects to componentDidMount, and set initial state in the constructor.
631
632 Please update the following components: App`,
633 `Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
634
635 * Move data fetching code or side effects to componentDidUpdate.
636 * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state
637
638 Please update the following components: Bar, Foo`,
639 `Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
640
641 * Move data fetching code or side effects to componentDidUpdate.
642
643 Please update the following components: App`,
644 ]);
645
646 // Dedupe
647 await act(() => root.render(<App />));
648 });
649
650 it('should coalesce warnings by lifecycle name', async () => {
651 function StrictRoot() {
652 return (
653 <React.StrictMode>
654 <App />
655 </React.StrictMode>
656 );
657 }
658 class App extends React.Component {
659 UNSAFE_componentWillMount() {}
660 UNSAFE_componentWillUpdate() {}
661 render() {
662 return <Parent />;
663 }
664 }
665 class Parent extends React.Component {
666 componentWillMount() {}
667 componentWillUpdate() {}
668 componentWillReceiveProps() {}
669 render() {
670 return <Child />;
671 }
672 }
673 class Child extends React.Component {
674 UNSAFE_componentWillReceiveProps() {}
675 render() {
676 return null;
677 }
678 }
679
680 const container = document.createElement('div');
681 const root = ReactDOMClient.createRoot(container);
682
683 await act(() => root.render(<StrictRoot />));
684 assertConsoleErrorDev([
685 `Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
686
687 * Move code with side effects to componentDidMount, and set initial state in the constructor.
688
689 Please update the following components: App`,
690 `Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
691
692 * Move data fetching code or side effects to componentDidUpdate.
693 * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state
694
695 Please update the following components: Child`,
696 `Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://react.dev/link/unsafe-component-lifecycles for details.
697
698 * Move data fetching code or side effects to componentDidUpdate.
699
700 Please update the following components: App`,
701 ]);
702 assertConsoleWarnDev([
703 `componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.
704
705 * Move code with side effects to componentDidMount, and set initial state in the constructor.
706 * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
707
708 Please update the following components: Parent`,
709 `componentWillReceiveProps has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.
710
711 * Move data fetching code or side effects to componentDidUpdate.
712 * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state
713 * Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
714
715 Please update the following components: Parent`,
716 `componentWillUpdate has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.
717
718 * Move data fetching code or side effects to componentDidUpdate.
719 * Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run \`npx react-codemod rename-unsafe-lifecycles\` in your project source folder.
720
721 Please update the following components: Parent`,
722 ]);
723 // Dedupe
724 await act(() => root.render(<StrictRoot />));
725 });
726
727 it('should warn about components not present during the initial render', async () => {
728 function StrictRoot({foo}) {
729 return <React.StrictMode>{foo ? <Foo /> : <Bar />}</React.StrictMode>;
730 }
731 class Foo extends React.Component {
732 UNSAFE_componentWillMount() {}
733 render() {
734 return null;
735 }
736 }
737 class Bar extends React.Component {
738 UNSAFE_componentWillMount() {}
739 render() {
740 return null;
741 }
742 }
743
744 const container = document.createElement('div');
745 const root = ReactDOMClient.createRoot(container);
746 await act(() => root.render(<StrictRoot foo={true} />));
747 assertConsoleErrorDev([
748 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
749 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
750 '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n\n' +
751 'Please update the following components: Foo',
752 ]);
753
754 await act(() => root.render(<StrictRoot foo={false} />));
755 assertConsoleErrorDev([
756 'Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' +
757 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
758 '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n\n' +
759 'Please update the following components: Bar',
760 ]);
761
762 // Dedupe
763 await act(() => root.render(<StrictRoot foo={true} />));
764 await act(() => root.render(<StrictRoot foo={false} />));
765 });
766
767 it('should also warn inside of "strict" mode trees', async () => {
768 const {StrictMode} = React;
769
770 class SyncRoot extends React.Component {
771 UNSAFE_componentWillMount() {}
772 UNSAFE_componentWillUpdate() {}
773 UNSAFE_componentWillReceiveProps() {}
774 render() {
775 return (
776 <StrictMode>
777 <Wrapper />
778 </StrictMode>
779 );
780 }
781 }
782 function Wrapper({children}) {
783 return (
784 <div>
785 <Bar />
786 <Foo />
787 </div>
788 );
789 }
790 class Foo extends React.Component {
791 UNSAFE_componentWillReceiveProps() {}
792 render() {
793 return null;
794 }
795 }
796 class Bar extends React.Component {
797 UNSAFE_componentWillReceiveProps() {}
798 render() {
799 return null;
800 }
801 }
802
803 const container = document.createElement('div');
804
805 const root = ReactDOMClient.createRoot(container);
806 await act(() => {
807 root.render(<SyncRoot />);
808 });
809 assertConsoleErrorDev([
810 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' +
811 'and may indicate bugs in your code. ' +
812 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
813 '* Move data fetching code or side effects to componentDidUpdate.\n' +
814 "* If you're updating state whenever props change, " +
815 'refactor your code to use memoization techniques or move it to ' +
816 'static getDerivedStateFromProps. Learn more at: https://react.dev/link/derived-state\n\n' +
817 'Please update the following components: Bar, Foo',
818 ]);
819
820 // Dedupe
821 await act(() => {
822 root.render(<SyncRoot />);
823 });
824 });
825 });
826
827 describe('symbol checks', () => {
828 beforeEach(() => {
829 jest.resetModules();
830 React = require('react');
831 ReactDOMClient = require('react-dom/client');
832 act = require('internal-test-utils').act;
833 });
834
835 it('should switch from StrictMode to a Fragment and reset state', async () => {
836 const {Fragment, StrictMode} = React;
837
838 function ParentComponent({useFragment}) {
839 return useFragment ? (
840 <Fragment>
841 <ChildComponent />
842 </Fragment>
843 ) : (
844 <StrictMode>
845 <ChildComponent />
846 </StrictMode>
847 );
848 }
849
850 class ChildComponent extends React.Component {
851 state = {
852 count: 0,
853 };
854 static getDerivedStateFromProps(nextProps, prevState) {
855 return {
856 count: prevState.count + 1,
857 };
858 }
859 render() {
860 return `count:${this.state.count}`;
861 }
862 }
863
864 const container = document.createElement('div');
865 const root = ReactDOMClient.createRoot(container);
866 await act(() => {
867 root.render(<ParentComponent useFragment={false} />);
868 });
869 expect(container.textContent).toBe('count:1');
870 await act(() => {
871 root.render(<ParentComponent useFragment={true} />);
872 });
873 expect(container.textContent).toBe('count:1');
874 });
875
876 it('should switch from a Fragment to StrictMode and reset state', async () => {
877 const {Fragment, StrictMode} = React;
878
879 function ParentComponent({useFragment}) {
880 return useFragment ? (
881 <Fragment>
882 <ChildComponent />
883 </Fragment>
884 ) : (
885 <StrictMode>
886 <ChildComponent />
887 </StrictMode>
888 );
889 }
890
891 class ChildComponent extends React.Component {
892 state = {
893 count: 0,
894 };
895 static getDerivedStateFromProps(nextProps, prevState) {
896 return {
897 count: prevState.count + 1,
898 };
899 }
900 render() {
901 return `count:${this.state.count}`;
902 }
903 }
904
905 const container = document.createElement('div');
906 const root = ReactDOMClient.createRoot(container);
907 await act(() => {
908 root.render(<ParentComponent useFragment={true} />);
909 });
910 expect(container.textContent).toBe('count:1');
911 await act(() => {
912 root.render(<ParentComponent useFragment={false} />);
913 });
914 expect(container.textContent).toBe('count:1');
915 });
916
917 it('should update with StrictMode without losing state', async () => {
918 const {StrictMode} = React;
919
920 function ParentComponent() {
921 return (
922 <StrictMode>
923 <ChildComponent />
924 </StrictMode>
925 );
926 }
927
928 class ChildComponent extends React.Component {
929 state = {
930 count: 0,
931 };
932 static getDerivedStateFromProps(nextProps, prevState) {
933 return {
934 count: prevState.count + 1,
935 };
936 }
937 render() {
938 return `count:${this.state.count}`;
939 }
940 }
941
942 const container = document.createElement('div');
943 const root = ReactDOMClient.createRoot(container);
944 await act(() => {
945 root.render(<ParentComponent />);
946 });
947 expect(container.textContent).toBe('count:1');
948 await act(() => {
949 root.render(<ParentComponent />);
950 });
951 expect(container.textContent).toBe('count:2');
952 });
953 });
954
955 describe('context legacy', () => {
956 beforeEach(() => {
957 jest.resetModules();
958 React = require('react');
959 ReactDOMClient = require('react-dom/client');
960 act = require('internal-test-utils').act;
961 PropTypes = require('prop-types');
962 });
963
964 afterEach(() => {
965 jest.restoreAllMocks();
966 });
967
968 // @gate !disableLegacyContext || !__DEV__
969 it('should warn if the legacy context API have been used in strict mode', async () => {
970 class LegacyContextProvider extends React.Component {
971 getChildContext() {
972 return {color: 'purple'};
973 }
974
975 render() {
976 return (
977 <div>
978 <LegacyContextConsumer />
979 <FunctionalLegacyContextConsumer />
980 </div>
981 );
982 }
983 }
984
985 function FunctionalLegacyContextConsumer() {
986 return null;
987 }
988
989 LegacyContextProvider.childContextTypes = {
990 color: PropTypes.string,
991 };
992
993 class LegacyContextConsumer extends React.Component {
994 render() {
995 return null;
996 }
997 }
998
999 const {StrictMode} = React;
1000
1001 class Root extends React.Component {
1002 render() {
1003 return (
1004 <div>
1005 <StrictMode>
1006 <LegacyContextProvider />
1007 </StrictMode>
1008 </div>
1009 );
1010 }
1011 }
1012
1013 LegacyContextConsumer.contextTypes = {
1014 color: PropTypes.string,
1015 };
1016
1017 FunctionalLegacyContextConsumer.contextTypes = {
1018 color: PropTypes.string,
1019 };
1020
1021 const container = document.createElement('div');
1022 const root = ReactDOMClient.createRoot(container);
1023 await act(() => {
1024 root.render(<Root />);
1025 });
1026
1027 assertConsoleErrorDev([
1028 'LegacyContextProvider uses the legacy childContextTypes API ' +
1029 'which will soon be removed. Use React.createContext() instead. ' +
1030 '(https://react.dev/link/legacy-context)' +
1031 '\n in Root (at **)',
1032 'LegacyContextConsumer uses the legacy contextTypes API which ' +
1033 'will soon be removed. Use React.createContext() with static ' +
1034 'contextType instead. (https://react.dev/link/legacy-context)' +
1035 '\n in LegacyContextProvider (at **)' +
1036 '\n in Root (at **)',
1037 'FunctionalLegacyContextConsumer uses the legacy contextTypes ' +
1038 'API which will be removed soon. Use React.createContext() ' +
1039 'with React.useContext() instead. (https://react.dev/link/legacy-context)' +
1040 '\n in LegacyContextProvider (at **)' +
1041 '\n in Root (at **)',
1042 'Legacy context API has been detected within a strict-mode tree.' +
1043 '\n\nThe old API will be supported in all 16.x releases, but applications ' +
1044 'using it should migrate to the new version.' +
1045 '\n\nPlease update the following components: ' +
1046 'FunctionalLegacyContextConsumer, LegacyContextConsumer, LegacyContextProvider' +
1047 '\n\nLearn more about this warning here: ' +
1048 'https://react.dev/link/legacy-context' +
1049 '\n in Root (at **)',
1050 ]);
1051
1052 // Dedupe
1053 await act(() => {
1054 root.render(<Root />);
1055 });
1056 });
1057
1058 describe('console logs logging', () => {
1059 beforeEach(() => {
1060 jest.resetModules();
1061 React = require('react');
1062 ReactDOMClient = require('react-dom/client');
1063 act = require('internal-test-utils').act;
1064
1065 // These tests are specifically testing console.log.
1066 spyOnDevAndProd(console, 'log').mockImplementation(() => {});
1067 });
1068
1069 afterEach(() => {
1070 console.log.mockRestore();
1071 });
1072
1073 it('does not disable logs for class double render', async () => {
1074 let count = 0;
1075 class Foo extends React.Component {
1076 render() {
1077 count++;
1078 console.log('foo ' + count);
1079 return null;
1080 }
1081 }
1082
1083 const container = document.createElement('div');
1084 const root = ReactDOMClient.createRoot(container);
1085 await act(() => {
1086 root.render(
1087 <React.StrictMode>
1088 <Foo />
1089 </React.StrictMode>,
1090 );
1091 });
1092 expect(count).toBe(__DEV__ ? 2 : 1);
1093 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1094 // Note: we should display the first log because otherwise
1095 // there is a risk of suppressing warnings when they happen,
1096 // and on the next render they'd get deduplicated and ignored.
1097 expect(console.log).toHaveBeenCalledWith('foo 1');
1098 });
1099
1100 it('does not disable logs for class double ctor', async () => {
1101 let count = 0;
1102 class Foo extends React.Component {
1103 constructor(props) {
1104 super(props);
1105 count++;
1106 console.log('foo ' + count);
1107 }
1108 render() {
1109 return null;
1110 }
1111 }
1112
1113 const container = document.createElement('div');
1114 const root = ReactDOMClient.createRoot(container);
1115 await act(() => {
1116 root.render(
1117 <React.StrictMode>
1118 <Foo />
1119 </React.StrictMode>,
1120 );
1121 });
1122 expect(count).toBe(__DEV__ ? 2 : 1);
1123 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1124 // Note: we should display the first log because otherwise
1125 // there is a risk of suppressing warnings when they happen,
1126 // and on the next render they'd get deduplicated and ignored.
1127 expect(console.log).toHaveBeenCalledWith('foo 1');
1128 });
1129
1130 it('does not disable logs for class double getDerivedStateFromProps', async () => {
1131 let count = 0;
1132 class Foo extends React.Component {
1133 state = {};
1134 static getDerivedStateFromProps() {
1135 count++;
1136 console.log('foo ' + count);
1137 return {};
1138 }
1139 render() {
1140 return null;
1141 }
1142 }
1143
1144 const container = document.createElement('div');
1145 const root = ReactDOMClient.createRoot(container);
1146 await act(() => {
1147 root.render(
1148 <React.StrictMode>
1149 <Foo />
1150 </React.StrictMode>,
1151 );
1152 });
1153 expect(count).toBe(__DEV__ ? 2 : 1);
1154 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1155 // Note: we should display the first log because otherwise
1156 // there is a risk of suppressing warnings when they happen,
1157 // and on the next render they'd get deduplicated and ignored.
1158 expect(console.log).toHaveBeenCalledWith('foo 1');
1159 });
1160
1161 it('does not disable logs for class double shouldComponentUpdate', async () => {
1162 let count = 0;
1163 class Foo extends React.Component {
1164 state = {};
1165 shouldComponentUpdate() {
1166 count++;
1167 console.log('foo ' + count);
1168 return {};
1169 }
1170 render() {
1171 return null;
1172 }
1173 }
1174
1175 const container = document.createElement('div');
1176 const root = ReactDOMClient.createRoot(container);
1177 await act(() => {
1178 root.render(
1179 <React.StrictMode>
1180 <Foo />
1181 </React.StrictMode>,
1182 );
1183 });
1184 await act(() => {
1185 root.render(
1186 <React.StrictMode>
1187 <Foo />
1188 </React.StrictMode>,
1189 );
1190 });
1191
1192 expect(count).toBe(__DEV__ ? 2 : 1);
1193 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1194 // Note: we should display the first log because otherwise
1195 // there is a risk of suppressing warnings when they happen,
1196 // and on the next render they'd get deduplicated and ignored.
1197 expect(console.log).toHaveBeenCalledWith('foo 1');
1198 });
1199
1200 it('does not disable logs for class state updaters', async () => {
1201 let inst;
1202 let count = 0;
1203 class Foo extends React.Component {
1204 state = {};
1205 render() {
1206 inst = this;
1207 return null;
1208 }
1209 }
1210
1211 const container = document.createElement('div');
1212 const root = ReactDOMClient.createRoot(container);
1213 await act(() => {
1214 root.render(
1215 <React.StrictMode>
1216 <Foo />
1217 </React.StrictMode>,
1218 );
1219 });
1220 await act(() => {
1221 inst.setState(() => {
1222 count++;
1223 console.log('foo ' + count);
1224 return {};
1225 });
1226 });
1227
1228 expect(count).toBe(__DEV__ ? 2 : 1);
1229 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1230 // Note: we should display the first log because otherwise
1231 // there is a risk of suppressing warnings when they happen,
1232 // and on the next render they'd get deduplicated and ignored.
1233 expect(console.log).toHaveBeenCalledWith('foo 1');
1234 });
1235
1236 it('does not disable logs for function double render', async () => {
1237 let count = 0;
1238 function Foo() {
1239 count++;
1240 console.log('foo ' + count);
1241 return null;
1242 }
1243
1244 const container = document.createElement('div');
1245 const root = ReactDOMClient.createRoot(container);
1246 await act(() => {
1247 root.render(
1248 <React.StrictMode>
1249 <Foo />
1250 </React.StrictMode>,
1251 );
1252 });
1253 expect(count).toBe(__DEV__ ? 2 : 1);
1254 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 2 : 1);
1255 // Note: we should display the first log because otherwise
1256 // there is a risk of suppressing warnings when they happen,
1257 // and on the next render they'd get deduplicated and ignored.
1258 expect(console.log).toHaveBeenCalledWith('foo 1');
1259 });
1260
1261 it('does not disable logs for effect double invoke', async () => {
1262 let create = 0;
1263 let cleanup = 0;
1264 function Foo() {
1265 React.useEffect(() => {
1266 create++;
1267 console.log('foo create ' + create);
1268 return () => {
1269 cleanup++;
1270 console.log('foo cleanup ' + cleanup);
1271 };
1272 });
1273 return null;
1274 }
1275
1276 const container = document.createElement('div');
1277 const root = ReactDOMClient.createRoot(container);
1278 await act(() => {
1279 root.render(
1280 <React.StrictMode>
1281 <Foo />
1282 </React.StrictMode>,
1283 );
1284 });
1285 expect(create).toBe(__DEV__ ? 2 : 1);
1286 expect(cleanup).toBe(__DEV__ ? 1 : 0);
1287 expect(console.log).toHaveBeenCalledTimes(__DEV__ ? 3 : 1);
1288 // Note: we should display the first log because otherwise
1289 // there is a risk of suppressing warnings when they happen,
1290 // and on the next render they'd get deduplicated and ignored.
1291 expect(console.log).toHaveBeenCalledWith('foo create 1');
1292 if (__DEV__) {
1293 expect(console.log).toHaveBeenCalledWith('foo cleanup 1');
1294 }
1295 });
1296 });
1297 });