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 ReactNoop;
    
  14. let Scheduler;
    
  15. let act;
    
  16. let assertLog;
    
  17. let waitFor;
    
  18. let waitForAll;
    
  19. let waitForPaint;
    
  20. 
    
  21. describe('StrictEffectsMode defaults', () => {
    
  22.   beforeEach(() => {
    
  23.     jest.resetModules();
    
  24. 
    
  25.     React = require('react');
    
  26.     ReactNoop = require('react-noop-renderer');
    
  27.     Scheduler = require('scheduler');
    
  28.     act = require('internal-test-utils').act;
    
  29. 
    
  30.     const InternalTestUtils = require('internal-test-utils');
    
  31.     waitFor = InternalTestUtils.waitFor;
    
  32.     waitForAll = InternalTestUtils.waitForAll;
    
  33.     waitForPaint = InternalTestUtils.waitForPaint;
    
  34.     assertLog = InternalTestUtils.assertLog;
    
  35. 
    
  36.     const ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  37.     ReactFeatureFlags.createRootStrictEffectsByDefault = __DEV__;
    
  38.   });
    
  39. 
    
  40.   it('should not double invoke effects in legacy mode', async () => {
    
  41.     function App({text}) {
    
  42.       React.useEffect(() => {
    
  43.         Scheduler.log('useEffect mount');
    
  44.         return () => Scheduler.log('useEffect unmount');
    
  45.       });
    
  46. 
    
  47.       React.useLayoutEffect(() => {
    
  48.         Scheduler.log('useLayoutEffect mount');
    
  49.         return () => Scheduler.log('useLayoutEffect unmount');
    
  50.       });
    
  51. 
    
  52.       return text;
    
  53.     }
    
  54. 
    
  55.     await act(() => {
    
  56.       ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
    
  57.     });
    
  58. 
    
  59.     assertLog(['useLayoutEffect mount', 'useEffect mount']);
    
  60.   });
    
  61. 
    
  62.   it('should not double invoke class lifecycles in legacy mode', async () => {
    
  63.     class App extends React.PureComponent {
    
  64.       componentDidMount() {
    
  65.         Scheduler.log('componentDidMount');
    
  66.       }
    
  67. 
    
  68.       componentDidUpdate() {
    
  69.         Scheduler.log('componentDidUpdate');
    
  70.       }
    
  71. 
    
  72.       componentWillUnmount() {
    
  73.         Scheduler.log('componentWillUnmount');
    
  74.       }
    
  75. 
    
  76.       render() {
    
  77.         return this.props.text;
    
  78.       }
    
  79.     }
    
  80. 
    
  81.     await act(() => {
    
  82.       ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
    
  83.     });
    
  84. 
    
  85.     assertLog(['componentDidMount']);
    
  86.   });
    
  87. 
    
  88.   if (__DEV__) {
    
  89.     it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', async () => {
    
  90.       function ComponentWithEffects({label}) {
    
  91.         React.useLayoutEffect(() => {
    
  92.           Scheduler.log(`useLayoutEffect mount "${label}"`);
    
  93.           return () => Scheduler.log(`useLayoutEffect unmount "${label}"`);
    
  94.         });
    
  95. 
    
  96.         return label;
    
  97.       }
    
  98. 
    
  99.       await act(async () => {
    
  100.         ReactNoop.render(
    
  101.           <>
    
  102.             <ComponentWithEffects label={'one'} />
    
  103.           </>,
    
  104.         );
    
  105. 
    
  106.         await waitForPaint([
    
  107.           'useLayoutEffect mount "one"',
    
  108.           'useLayoutEffect unmount "one"',
    
  109.           'useLayoutEffect mount "one"',
    
  110.         ]);
    
  111.       });
    
  112. 
    
  113.       await act(async () => {
    
  114.         ReactNoop.render(
    
  115.           <>
    
  116.             <ComponentWithEffects label={'one'} />
    
  117.             <ComponentWithEffects label={'two'} />
    
  118.           </>,
    
  119.         );
    
  120. 
    
  121.         assertLog([]);
    
  122.         await waitForPaint([
    
  123.           // Cleanup and re-run "one" (and "two") since there is no dependencies array.
    
  124.           'useLayoutEffect unmount "one"',
    
  125.           'useLayoutEffect mount "one"',
    
  126.           'useLayoutEffect mount "two"',
    
  127. 
    
  128.           // Since "two" is new, it should be double-invoked.
    
  129.           'useLayoutEffect unmount "two"',
    
  130.           'useLayoutEffect mount "two"',
    
  131.         ]);
    
  132.       });
    
  133.     });
    
  134. 
    
  135.     // This test also verifies that double-invoked effects flush synchronously
    
  136.     // within the same frame as passive effects.
    
  137.     it('should double invoke effects only for newly mounted components', async () => {
    
  138.       function ComponentWithEffects({label}) {
    
  139.         React.useEffect(() => {
    
  140.           Scheduler.log(`useEffect mount "${label}"`);
    
  141.           return () => Scheduler.log(`useEffect unmount "${label}"`);
    
  142.         });
    
  143. 
    
  144.         React.useLayoutEffect(() => {
    
  145.           Scheduler.log(`useLayoutEffect mount "${label}"`);
    
  146.           return () => Scheduler.log(`useLayoutEffect unmount "${label}"`);
    
  147.         });
    
  148. 
    
  149.         return label;
    
  150.       }
    
  151. 
    
  152.       await act(async () => {
    
  153.         ReactNoop.render(
    
  154.           <>
    
  155.             <ComponentWithEffects label={'one'} />
    
  156.           </>,
    
  157.         );
    
  158. 
    
  159.         await waitForAll([
    
  160.           'useLayoutEffect mount "one"',
    
  161.           'useEffect mount "one"',
    
  162.           'useLayoutEffect unmount "one"',
    
  163.           'useEffect unmount "one"',
    
  164.           'useLayoutEffect mount "one"',
    
  165.           'useEffect mount "one"',
    
  166.         ]);
    
  167.       });
    
  168. 
    
  169.       await act(async () => {
    
  170.         ReactNoop.render(
    
  171.           <>
    
  172.             <ComponentWithEffects label={'one'} />
    
  173.             <ComponentWithEffects label={'two'} />
    
  174.           </>,
    
  175.         );
    
  176. 
    
  177.         await waitFor([
    
  178.           // Cleanup and re-run "one" (and "two") since there is no dependencies array.
    
  179.           'useLayoutEffect unmount "one"',
    
  180.           'useLayoutEffect mount "one"',
    
  181.           'useLayoutEffect mount "two"',
    
  182.         ]);
    
  183.         await waitForAll([
    
  184.           'useEffect unmount "one"',
    
  185.           'useEffect mount "one"',
    
  186.           'useEffect mount "two"',
    
  187. 
    
  188.           // Since "two" is new, it should be double-invoked.
    
  189.           'useLayoutEffect unmount "two"',
    
  190.           'useEffect unmount "two"',
    
  191.           'useLayoutEffect mount "two"',
    
  192.           'useEffect mount "two"',
    
  193.         ]);
    
  194.       });
    
  195.     });
    
  196. 
    
  197.     it('double invoking for effects for modern roots', async () => {
    
  198.       function App({text}) {
    
  199.         React.useEffect(() => {
    
  200.           Scheduler.log('useEffect mount');
    
  201.           return () => Scheduler.log('useEffect unmount');
    
  202.         });
    
  203. 
    
  204.         React.useLayoutEffect(() => {
    
  205.           Scheduler.log('useLayoutEffect mount');
    
  206.           return () => Scheduler.log('useLayoutEffect unmount');
    
  207.         });
    
  208. 
    
  209.         return text;
    
  210.       }
    
  211.       await act(() => {
    
  212.         ReactNoop.render(<App text={'mount'} />);
    
  213.       });
    
  214. 
    
  215.       assertLog([
    
  216.         'useLayoutEffect mount',
    
  217.         'useEffect mount',
    
  218.         'useLayoutEffect unmount',
    
  219.         'useEffect unmount',
    
  220.         'useLayoutEffect mount',
    
  221.         'useEffect mount',
    
  222.       ]);
    
  223. 
    
  224.       await act(() => {
    
  225.         ReactNoop.render(<App text={'update'} />);
    
  226.       });
    
  227. 
    
  228.       assertLog([
    
  229.         'useLayoutEffect unmount',
    
  230.         'useLayoutEffect mount',
    
  231.         'useEffect unmount',
    
  232.         'useEffect mount',
    
  233.       ]);
    
  234. 
    
  235.       await act(() => {
    
  236.         ReactNoop.render(null);
    
  237.       });
    
  238. 
    
  239.       assertLog(['useLayoutEffect unmount', 'useEffect unmount']);
    
  240.     });
    
  241. 
    
  242.     it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
    
  243.       function App({text}) {
    
  244.         React.useEffect(() => {
    
  245.           Scheduler.log('useEffect One mount');
    
  246.           return () => Scheduler.log('useEffect One unmount');
    
  247.         });
    
  248. 
    
  249.         React.useEffect(() => {
    
  250.           Scheduler.log('useEffect Two mount');
    
  251.           return () => Scheduler.log('useEffect Two unmount');
    
  252.         });
    
  253. 
    
  254.         return text;
    
  255.       }
    
  256. 
    
  257.       await act(() => {
    
  258.         ReactNoop.render(<App text={'mount'} />);
    
  259.       });
    
  260. 
    
  261.       assertLog([
    
  262.         'useEffect One mount',
    
  263.         'useEffect Two mount',
    
  264.         'useEffect One unmount',
    
  265.         'useEffect Two unmount',
    
  266.         'useEffect One mount',
    
  267.         'useEffect Two mount',
    
  268.       ]);
    
  269. 
    
  270.       await act(() => {
    
  271.         ReactNoop.render(<App text={'update'} />);
    
  272.       });
    
  273. 
    
  274.       assertLog([
    
  275.         'useEffect One unmount',
    
  276.         'useEffect Two unmount',
    
  277.         'useEffect One mount',
    
  278.         'useEffect Two mount',
    
  279.       ]);
    
  280. 
    
  281.       await act(() => {
    
  282.         ReactNoop.render(null);
    
  283.       });
    
  284. 
    
  285.       assertLog(['useEffect One unmount', 'useEffect Two unmount']);
    
  286.     });
    
  287. 
    
  288.     it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', async () => {
    
  289.       function App({text}) {
    
  290.         React.useLayoutEffect(() => {
    
  291.           Scheduler.log('useLayoutEffect One mount');
    
  292.           return () => Scheduler.log('useLayoutEffect One unmount');
    
  293.         });
    
  294. 
    
  295.         React.useLayoutEffect(() => {
    
  296.           Scheduler.log('useLayoutEffect Two mount');
    
  297.           return () => Scheduler.log('useLayoutEffect Two unmount');
    
  298.         });
    
  299. 
    
  300.         return text;
    
  301.       }
    
  302. 
    
  303.       await act(() => {
    
  304.         ReactNoop.render(<App text={'mount'} />);
    
  305.       });
    
  306. 
    
  307.       assertLog([
    
  308.         'useLayoutEffect One mount',
    
  309.         'useLayoutEffect Two mount',
    
  310.         'useLayoutEffect One unmount',
    
  311.         'useLayoutEffect Two unmount',
    
  312.         'useLayoutEffect One mount',
    
  313.         'useLayoutEffect Two mount',
    
  314.       ]);
    
  315. 
    
  316.       await act(() => {
    
  317.         ReactNoop.render(<App text={'update'} />);
    
  318.       });
    
  319. 
    
  320.       assertLog([
    
  321.         'useLayoutEffect One unmount',
    
  322.         'useLayoutEffect Two unmount',
    
  323.         'useLayoutEffect One mount',
    
  324.         'useLayoutEffect Two mount',
    
  325.       ]);
    
  326. 
    
  327.       await act(() => {
    
  328.         ReactNoop.render(null);
    
  329.       });
    
  330. 
    
  331.       assertLog(['useLayoutEffect One unmount', 'useLayoutEffect Two unmount']);
    
  332.     });
    
  333. 
    
  334.     it('useEffect and useLayoutEffect is called twice when there is no unmount', async () => {
    
  335.       function App({text}) {
    
  336.         React.useEffect(() => {
    
  337.           Scheduler.log('useEffect mount');
    
  338.         });
    
  339. 
    
  340.         React.useLayoutEffect(() => {
    
  341.           Scheduler.log('useLayoutEffect mount');
    
  342.         });
    
  343. 
    
  344.         return text;
    
  345.       }
    
  346. 
    
  347.       await act(() => {
    
  348.         ReactNoop.render(<App text={'mount'} />);
    
  349.       });
    
  350. 
    
  351.       assertLog([
    
  352.         'useLayoutEffect mount',
    
  353.         'useEffect mount',
    
  354.         'useLayoutEffect mount',
    
  355.         'useEffect mount',
    
  356.       ]);
    
  357. 
    
  358.       await act(() => {
    
  359.         ReactNoop.render(<App text={'update'} />);
    
  360.       });
    
  361. 
    
  362.       assertLog(['useLayoutEffect mount', 'useEffect mount']);
    
  363. 
    
  364.       await act(() => {
    
  365.         ReactNoop.render(null);
    
  366.       });
    
  367. 
    
  368.       assertLog([]);
    
  369.     });
    
  370. 
    
  371.     //@gate useModernStrictMode
    
  372.     it('disconnects refs during double invoking', async () => {
    
  373.       const onRefMock = jest.fn();
    
  374.       function App({text}) {
    
  375.         return (
    
  376.           <span
    
  377.             ref={ref => {
    
  378.               onRefMock(ref);
    
  379.             }}>
    
  380.             text
    
  381.           </span>
    
  382.         );
    
  383.       }
    
  384. 
    
  385.       await act(() => {
    
  386.         ReactNoop.render(<App text={'mount'} />);
    
  387.       });
    
  388. 
    
  389.       expect(onRefMock.mock.calls.length).toBe(3);
    
  390.       expect(onRefMock.mock.calls[0][0]).not.toBeNull();
    
  391.       expect(onRefMock.mock.calls[1][0]).toBe(null);
    
  392.       expect(onRefMock.mock.calls[2][0]).not.toBeNull();
    
  393.     });
    
  394. 
    
  395.     it('passes the right context to class component lifecycles', async () => {
    
  396.       class App extends React.PureComponent {
    
  397.         test() {}
    
  398. 
    
  399.         componentDidMount() {
    
  400.           this.test();
    
  401.           Scheduler.log('componentDidMount');
    
  402.         }
    
  403. 
    
  404.         componentDidUpdate() {
    
  405.           this.test();
    
  406.           Scheduler.log('componentDidUpdate');
    
  407.         }
    
  408. 
    
  409.         componentWillUnmount() {
    
  410.           this.test();
    
  411.           Scheduler.log('componentWillUnmount');
    
  412.         }
    
  413. 
    
  414.         render() {
    
  415.           return null;
    
  416.         }
    
  417.       }
    
  418. 
    
  419.       await act(() => {
    
  420.         ReactNoop.render(<App />);
    
  421.       });
    
  422. 
    
  423.       assertLog([
    
  424.         'componentDidMount',
    
  425.         'componentWillUnmount',
    
  426.         'componentDidMount',
    
  427.       ]);
    
  428.     });
    
  429. 
    
  430.     it('double invoking works for class components', async () => {
    
  431.       class App extends React.PureComponent {
    
  432.         componentDidMount() {
    
  433.           Scheduler.log('componentDidMount');
    
  434.         }
    
  435. 
    
  436.         componentDidUpdate() {
    
  437.           Scheduler.log('componentDidUpdate');
    
  438.         }
    
  439. 
    
  440.         componentWillUnmount() {
    
  441.           Scheduler.log('componentWillUnmount');
    
  442.         }
    
  443. 
    
  444.         render() {
    
  445.           return this.props.text;
    
  446.         }
    
  447.       }
    
  448. 
    
  449.       await act(() => {
    
  450.         ReactNoop.render(<App text={'mount'} />);
    
  451.       });
    
  452. 
    
  453.       assertLog([
    
  454.         'componentDidMount',
    
  455.         'componentWillUnmount',
    
  456.         'componentDidMount',
    
  457.       ]);
    
  458. 
    
  459.       await act(() => {
    
  460.         ReactNoop.render(<App text={'update'} />);
    
  461.       });
    
  462. 
    
  463.       assertLog(['componentDidUpdate']);
    
  464. 
    
  465.       await act(() => {
    
  466.         ReactNoop.render(null);
    
  467.       });
    
  468. 
    
  469.       assertLog(['componentWillUnmount']);
    
  470.     });
    
  471. 
    
  472.     it('double flushing passive effects only results in one double invoke', async () => {
    
  473.       function App({text}) {
    
  474.         const [state, setState] = React.useState(0);
    
  475.         React.useEffect(() => {
    
  476.           if (state !== 1) {
    
  477.             setState(1);
    
  478.           }
    
  479.           Scheduler.log('useEffect mount');
    
  480.           return () => Scheduler.log('useEffect unmount');
    
  481.         });
    
  482. 
    
  483.         React.useLayoutEffect(() => {
    
  484.           Scheduler.log('useLayoutEffect mount');
    
  485.           return () => Scheduler.log('useLayoutEffect unmount');
    
  486.         });
    
  487. 
    
  488.         Scheduler.log(text);
    
  489.         return text;
    
  490.       }
    
  491. 
    
  492.       await act(() => {
    
  493.         ReactNoop.render(<App text={'mount'} />);
    
  494.       });
    
  495. 
    
  496.       assertLog([
    
  497.         'mount',
    
  498.         'useLayoutEffect mount',
    
  499.         'useEffect mount',
    
  500.         'useLayoutEffect unmount',
    
  501.         'useEffect unmount',
    
  502.         'useLayoutEffect mount',
    
  503.         'useEffect mount',
    
  504.         'mount',
    
  505.         'useLayoutEffect unmount',
    
  506.         'useLayoutEffect mount',
    
  507.         'useEffect unmount',
    
  508.         'useEffect mount',
    
  509.       ]);
    
  510.     });
    
  511. 
    
  512.     it('newly mounted components after initial mount get double invoked', async () => {
    
  513.       let _setShowChild;
    
  514.       function Child() {
    
  515.         React.useEffect(() => {
    
  516.           Scheduler.log('Child useEffect mount');
    
  517.           return () => Scheduler.log('Child useEffect unmount');
    
  518.         });
    
  519.         React.useLayoutEffect(() => {
    
  520.           Scheduler.log('Child useLayoutEffect mount');
    
  521.           return () => Scheduler.log('Child useLayoutEffect unmount');
    
  522.         });
    
  523. 
    
  524.         return null;
    
  525.       }
    
  526. 
    
  527.       function App() {
    
  528.         const [showChild, setShowChild] = React.useState(false);
    
  529.         _setShowChild = setShowChild;
    
  530.         React.useEffect(() => {
    
  531.           Scheduler.log('App useEffect mount');
    
  532.           return () => Scheduler.log('App useEffect unmount');
    
  533.         });
    
  534.         React.useLayoutEffect(() => {
    
  535.           Scheduler.log('App useLayoutEffect mount');
    
  536.           return () => Scheduler.log('App useLayoutEffect unmount');
    
  537.         });
    
  538. 
    
  539.         return showChild && <Child />;
    
  540.       }
    
  541. 
    
  542.       await act(() => {
    
  543.         ReactNoop.render(<App />);
    
  544.       });
    
  545. 
    
  546.       assertLog([
    
  547.         'App useLayoutEffect mount',
    
  548.         'App useEffect mount',
    
  549.         'App useLayoutEffect unmount',
    
  550.         'App useEffect unmount',
    
  551.         'App useLayoutEffect mount',
    
  552.         'App useEffect mount',
    
  553.       ]);
    
  554. 
    
  555.       await act(() => {
    
  556.         _setShowChild(true);
    
  557.       });
    
  558. 
    
  559.       assertLog([
    
  560.         'App useLayoutEffect unmount',
    
  561.         'Child useLayoutEffect mount',
    
  562.         'App useLayoutEffect mount',
    
  563.         'App useEffect unmount',
    
  564.         'Child useEffect mount',
    
  565.         'App useEffect mount',
    
  566.         'Child useLayoutEffect unmount',
    
  567.         'Child useEffect unmount',
    
  568.         'Child useLayoutEffect mount',
    
  569.         'Child useEffect mount',
    
  570.       ]);
    
  571.     });
    
  572. 
    
  573.     it('classes and functions are double invoked together correctly', async () => {
    
  574.       class ClassChild extends React.PureComponent {
    
  575.         componentDidMount() {
    
  576.           Scheduler.log('componentDidMount');
    
  577.         }
    
  578. 
    
  579.         componentWillUnmount() {
    
  580.           Scheduler.log('componentWillUnmount');
    
  581.         }
    
  582. 
    
  583.         render() {
    
  584.           return this.props.text;
    
  585.         }
    
  586.       }
    
  587. 
    
  588.       function FunctionChild({text}) {
    
  589.         React.useEffect(() => {
    
  590.           Scheduler.log('useEffect mount');
    
  591.           return () => Scheduler.log('useEffect unmount');
    
  592.         });
    
  593.         React.useLayoutEffect(() => {
    
  594.           Scheduler.log('useLayoutEffect mount');
    
  595.           return () => Scheduler.log('useLayoutEffect unmount');
    
  596.         });
    
  597.         return text;
    
  598.       }
    
  599. 
    
  600.       function App({text}) {
    
  601.         return (
    
  602.           <>
    
  603.             <ClassChild text={text} />
    
  604.             <FunctionChild text={text} />
    
  605.           </>
    
  606.         );
    
  607.       }
    
  608. 
    
  609.       await act(() => {
    
  610.         ReactNoop.render(<App text={'mount'} />);
    
  611.       });
    
  612. 
    
  613.       assertLog([
    
  614.         'componentDidMount',
    
  615.         'useLayoutEffect mount',
    
  616.         'useEffect mount',
    
  617.         'componentWillUnmount',
    
  618.         'useLayoutEffect unmount',
    
  619.         'useEffect unmount',
    
  620.         'componentDidMount',
    
  621.         'useLayoutEffect mount',
    
  622.         'useEffect mount',
    
  623.       ]);
    
  624. 
    
  625.       await act(() => {
    
  626.         ReactNoop.render(<App text={'mount'} />);
    
  627.       });
    
  628. 
    
  629.       assertLog([
    
  630.         'useLayoutEffect unmount',
    
  631.         'useLayoutEffect mount',
    
  632.         'useEffect unmount',
    
  633.         'useEffect mount',
    
  634.       ]);
    
  635. 
    
  636.       await act(() => {
    
  637.         ReactNoop.render(null);
    
  638.       });
    
  639. 
    
  640.       assertLog([
    
  641.         'componentWillUnmount',
    
  642.         'useLayoutEffect unmount',
    
  643.         'useEffect unmount',
    
  644.       ]);
    
  645.     });
    
  646.   }
    
  647. });