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.  * @jest-environment node
    
  9.  */
    
  10. 
    
  11. /* eslint-disable no-func-assign */
    
  12. 
    
  13. 'use strict';
    
  14. 
    
  15. describe('useRef', () => {
    
  16.   let React;
    
  17.   let ReactNoop;
    
  18.   let Scheduler;
    
  19.   let act;
    
  20.   let useCallback;
    
  21.   let useEffect;
    
  22.   let useLayoutEffect;
    
  23.   let useRef;
    
  24.   let useState;
    
  25.   let waitForAll;
    
  26.   let assertLog;
    
  27. 
    
  28.   beforeEach(() => {
    
  29.     React = require('react');
    
  30.     ReactNoop = require('react-noop-renderer');
    
  31.     Scheduler = require('scheduler');
    
  32. 
    
  33.     const ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  34.     ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
    
  35. 
    
  36.     act = require('internal-test-utils').act;
    
  37.     useCallback = React.useCallback;
    
  38.     useEffect = React.useEffect;
    
  39.     useLayoutEffect = React.useLayoutEffect;
    
  40.     useRef = React.useRef;
    
  41.     useState = React.useState;
    
  42. 
    
  43.     const InternalTestUtils = require('internal-test-utils');
    
  44.     waitForAll = InternalTestUtils.waitForAll;
    
  45.     assertLog = InternalTestUtils.assertLog;
    
  46.   });
    
  47. 
    
  48.   function Text(props) {
    
  49.     Scheduler.log(props.text);
    
  50.     return <span prop={props.text} />;
    
  51.   }
    
  52. 
    
  53.   it('creates a ref object initialized with the provided value', async () => {
    
  54.     jest.useFakeTimers();
    
  55. 
    
  56.     function useDebouncedCallback(callback, ms, inputs) {
    
  57.       const timeoutID = useRef(-1);
    
  58.       useEffect(() => {
    
  59.         return function unmount() {
    
  60.           clearTimeout(timeoutID.current);
    
  61.         };
    
  62.       }, []);
    
  63.       const debouncedCallback = useCallback(
    
  64.         (...args) => {
    
  65.           clearTimeout(timeoutID.current);
    
  66.           timeoutID.current = setTimeout(callback, ms, ...args);
    
  67.         },
    
  68.         [callback, ms],
    
  69.       );
    
  70.       return useCallback(debouncedCallback, inputs);
    
  71.     }
    
  72. 
    
  73.     let ping;
    
  74.     function App() {
    
  75.       ping = useDebouncedCallback(
    
  76.         value => {
    
  77.           Scheduler.log('ping: ' + value);
    
  78.         },
    
  79.         100,
    
  80.         [],
    
  81.       );
    
  82.       return null;
    
  83.     }
    
  84. 
    
  85.     await act(() => {
    
  86.       ReactNoop.render(<App />);
    
  87.     });
    
  88.     assertLog([]);
    
  89. 
    
  90.     ping(1);
    
  91.     ping(2);
    
  92.     ping(3);
    
  93. 
    
  94.     assertLog([]);
    
  95. 
    
  96.     jest.advanceTimersByTime(100);
    
  97. 
    
  98.     assertLog(['ping: 3']);
    
  99. 
    
  100.     ping(4);
    
  101.     jest.advanceTimersByTime(20);
    
  102.     ping(5);
    
  103.     ping(6);
    
  104.     jest.advanceTimersByTime(80);
    
  105. 
    
  106.     assertLog([]);
    
  107. 
    
  108.     jest.advanceTimersByTime(20);
    
  109.     assertLog(['ping: 6']);
    
  110.   });
    
  111. 
    
  112.   it('should return the same ref during re-renders', async () => {
    
  113.     function Counter() {
    
  114.       const ref = useRef('val');
    
  115.       const [count, setCount] = useState(0);
    
  116.       const [firstRef] = useState(ref);
    
  117. 
    
  118.       if (firstRef !== ref) {
    
  119.         throw new Error('should never change');
    
  120.       }
    
  121. 
    
  122.       if (count < 3) {
    
  123.         setCount(count + 1);
    
  124.       }
    
  125. 
    
  126.       return <Text text={count} />;
    
  127.     }
    
  128. 
    
  129.     ReactNoop.render(<Counter />);
    
  130.     await waitForAll([3]);
    
  131. 
    
  132.     ReactNoop.render(<Counter />);
    
  133.     await waitForAll([3]);
    
  134.   });
    
  135. 
    
  136.   if (__DEV__) {
    
  137.     it('should never warn when attaching to children', async () => {
    
  138.       class Component extends React.Component {
    
  139.         render() {
    
  140.           return null;
    
  141.         }
    
  142.       }
    
  143. 
    
  144.       function Example({phase}) {
    
  145.         const hostRef = useRef();
    
  146.         const classRef = useRef();
    
  147.         return (
    
  148.           <>
    
  149.             <div key={`host-${phase}`} ref={hostRef} />
    
  150.             <Component key={`class-${phase}`} ref={classRef} />
    
  151.           </>
    
  152.         );
    
  153.       }
    
  154. 
    
  155.       await act(() => {
    
  156.         ReactNoop.render(<Example phase="mount" />);
    
  157.       });
    
  158.       await act(() => {
    
  159.         ReactNoop.render(<Example phase="update" />);
    
  160.       });
    
  161.     });
    
  162. 
    
  163.     // @gate enableUseRefAccessWarning
    
  164.     it('should warn about reads during render', async () => {
    
  165.       function Example() {
    
  166.         const ref = useRef(123);
    
  167.         let value;
    
  168.         expect(() => {
    
  169.           value = ref.current;
    
  170.         }).toWarnDev([
    
  171.           'Example: Unsafe read of a mutable value during render.',
    
  172.         ]);
    
  173.         return value;
    
  174.       }
    
  175. 
    
  176.       await act(() => {
    
  177.         ReactNoop.render(<Example />);
    
  178.       });
    
  179.     });
    
  180. 
    
  181.     it('should not warn about lazy init during render', async () => {
    
  182.       function Example() {
    
  183.         const ref1 = useRef(null);
    
  184.         const ref2 = useRef(undefined);
    
  185.         // Read: safe because lazy init:
    
  186.         if (ref1.current === null) {
    
  187.           ref1.current = 123;
    
  188.         }
    
  189.         if (ref2.current === undefined) {
    
  190.           ref2.current = 123;
    
  191.         }
    
  192.         return null;
    
  193.       }
    
  194. 
    
  195.       await act(() => {
    
  196.         ReactNoop.render(<Example />);
    
  197.       });
    
  198. 
    
  199.       // Should not warn after an update either.
    
  200.       await act(() => {
    
  201.         ReactNoop.render(<Example />);
    
  202.       });
    
  203.     });
    
  204. 
    
  205.     it('should not warn about lazy init outside of render', async () => {
    
  206.       function Example() {
    
  207.         // eslint-disable-next-line no-unused-vars
    
  208.         const [didMount, setDidMount] = useState(false);
    
  209.         const ref1 = useRef(null);
    
  210.         const ref2 = useRef(undefined);
    
  211.         useLayoutEffect(() => {
    
  212.           ref1.current = 123;
    
  213.           ref2.current = 123;
    
  214.           setDidMount(true);
    
  215.         }, []);
    
  216.         return null;
    
  217.       }
    
  218. 
    
  219.       await act(() => {
    
  220.         ReactNoop.render(<Example />);
    
  221.       });
    
  222.     });
    
  223. 
    
  224.     // @gate enableUseRefAccessWarning
    
  225.     it('should warn about unconditional lazy init during render', async () => {
    
  226.       function Example() {
    
  227.         const ref1 = useRef(null);
    
  228.         const ref2 = useRef(undefined);
    
  229. 
    
  230.         if (shouldExpectWarning) {
    
  231.           expect(() => {
    
  232.             ref1.current = 123;
    
  233.           }).toWarnDev([
    
  234.             'Example: Unsafe write of a mutable value during render',
    
  235.           ]);
    
  236.           expect(() => {
    
  237.             ref2.current = 123;
    
  238.           }).toWarnDev([
    
  239.             'Example: Unsafe write of a mutable value during render',
    
  240.           ]);
    
  241.         } else {
    
  242.           ref1.current = 123;
    
  243.           ref1.current = 123;
    
  244.         }
    
  245. 
    
  246.         // But only warn once
    
  247.         ref1.current = 345;
    
  248.         ref1.current = 345;
    
  249. 
    
  250.         return null;
    
  251.       }
    
  252. 
    
  253.       let shouldExpectWarning = true;
    
  254.       await act(() => {
    
  255.         ReactNoop.render(<Example />);
    
  256.       });
    
  257. 
    
  258.       // Should not warn again on update.
    
  259.       shouldExpectWarning = false;
    
  260.       await act(() => {
    
  261.         ReactNoop.render(<Example />);
    
  262.       });
    
  263.     });
    
  264. 
    
  265.     // @gate enableUseRefAccessWarning
    
  266.     it('should warn about reads to ref after lazy init pattern', async () => {
    
  267.       function Example() {
    
  268.         const ref1 = useRef(null);
    
  269.         const ref2 = useRef(undefined);
    
  270. 
    
  271.         // Read 1: safe because lazy init:
    
  272.         if (ref1.current === null) {
    
  273.           ref1.current = 123;
    
  274.         }
    
  275.         if (ref2.current === undefined) {
    
  276.           ref2.current = 123;
    
  277.         }
    
  278. 
    
  279.         let value;
    
  280.         expect(() => {
    
  281.           value = ref1.current;
    
  282.         }).toWarnDev(['Example: Unsafe read of a mutable value during render']);
    
  283.         expect(() => {
    
  284.           value = ref2.current;
    
  285.         }).toWarnDev(['Example: Unsafe read of a mutable value during render']);
    
  286. 
    
  287.         // But it should only warn once.
    
  288.         value = ref1.current;
    
  289.         value = ref2.current;
    
  290. 
    
  291.         return value;
    
  292.       }
    
  293. 
    
  294.       await act(() => {
    
  295.         ReactNoop.render(<Example />);
    
  296.       });
    
  297.     });
    
  298. 
    
  299.     // @gate enableUseRefAccessWarning
    
  300.     it('should warn about writes to ref after lazy init pattern', async () => {
    
  301.       function Example() {
    
  302.         const ref1 = useRef(null);
    
  303.         const ref2 = useRef(undefined);
    
  304.         // Read: safe because lazy init:
    
  305.         if (ref1.current === null) {
    
  306.           ref1.current = 123;
    
  307.         }
    
  308.         if (ref2.current === undefined) {
    
  309.           ref2.current = 123;
    
  310.         }
    
  311. 
    
  312.         expect(() => {
    
  313.           ref1.current = 456;
    
  314.         }).toWarnDev([
    
  315.           'Example: Unsafe write of a mutable value during render',
    
  316.         ]);
    
  317.         expect(() => {
    
  318.           ref2.current = 456;
    
  319.         }).toWarnDev([
    
  320.           'Example: Unsafe write of a mutable value during render',
    
  321.         ]);
    
  322. 
    
  323.         return null;
    
  324.       }
    
  325. 
    
  326.       await act(() => {
    
  327.         ReactNoop.render(<Example />);
    
  328.       });
    
  329.     });
    
  330. 
    
  331.     it('should not warn about reads or writes within effect', async () => {
    
  332.       function Example() {
    
  333.         const ref = useRef(123);
    
  334.         useLayoutEffect(() => {
    
  335.           expect(ref.current).toBe(123);
    
  336.           ref.current = 456;
    
  337.           expect(ref.current).toBe(456);
    
  338.         }, []);
    
  339.         useEffect(() => {
    
  340.           expect(ref.current).toBe(456);
    
  341.           ref.current = 789;
    
  342.           expect(ref.current).toBe(789);
    
  343.         }, []);
    
  344.         return null;
    
  345.       }
    
  346. 
    
  347.       await act(() => {
    
  348.         ReactNoop.render(<Example />);
    
  349.       });
    
  350. 
    
  351.       ReactNoop.flushPassiveEffects();
    
  352.     });
    
  353. 
    
  354.     it('should not warn about reads or writes outside of render phase (e.g. event handler)', async () => {
    
  355.       let ref;
    
  356.       function Example() {
    
  357.         ref = useRef(123);
    
  358.         return null;
    
  359.       }
    
  360. 
    
  361.       await act(() => {
    
  362.         ReactNoop.render(<Example />);
    
  363.       });
    
  364. 
    
  365.       expect(ref.current).toBe(123);
    
  366.       ref.current = 456;
    
  367.       expect(ref.current).toBe(456);
    
  368.     });
    
  369.   }
    
  370. });