/*** Copyright (c) Meta Platforms, Inc. and affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @emails react-core* @jest-environment node*//* eslint-disable no-func-assign */'use strict';
describe('useRef', () => {
let React;
let ReactNoop;
let Scheduler;
let act;
let useCallback;
let useEffect;
let useLayoutEffect;
let useRef;
let useState;
let waitForAll;
let assertLog;
beforeEach(() => {
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
act = require('internal-test-utils').act;
useCallback = React.useCallback;
useEffect = React.useEffect;
useLayoutEffect = React.useLayoutEffect;
useRef = React.useRef;
useState = React.useState;
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
});function Text(props) {
Scheduler.log(props.text);
return <span prop={props.text} />;
}it('creates a ref object initialized with the provided value', async () => {
jest.useFakeTimers();
function useDebouncedCallback(callback, ms, inputs) {
const timeoutID = useRef(-1);
useEffect(() => {
return function unmount() {
clearTimeout(timeoutID.current);
};}, []);const debouncedCallback = useCallback(
(...args) => {
clearTimeout(timeoutID.current);
timeoutID.current = setTimeout(callback, ms, ...args);
},[callback, ms],
);return useCallback(debouncedCallback, inputs);
}let ping;
function App() {
ping = useDebouncedCallback(
value => {
Scheduler.log('ping: ' + value);
},100,
[],);return null;
}await act(() => {
ReactNoop.render(<App />);
});assertLog([]);
ping(1);
ping(2);
ping(3);
assertLog([]);
jest.advanceTimersByTime(100);
assertLog(['ping: 3']);
ping(4);
jest.advanceTimersByTime(20);
ping(5);
ping(6);
jest.advanceTimersByTime(80);
assertLog([]);
jest.advanceTimersByTime(20);
assertLog(['ping: 6']);
});it('should return the same ref during re-renders', async () => {
function Counter() {
const ref = useRef('val');
const [count, setCount] = useState(0);
const [firstRef] = useState(ref);
if (firstRef !== ref) {
throw new Error('should never change');
}if (count < 3) {
setCount(count + 1);
}return <Text text={count} />;
}ReactNoop.render(<Counter />);
await waitForAll([3]);
ReactNoop.render(<Counter />);
await waitForAll([3]);
});if (__DEV__) {
it('should never warn when attaching to children', async () => {
class Component extends React.Component {render() {
return null;
}}function Example({phase}) {
const hostRef = useRef();
const classRef = useRef();
return (
<><div key={`host-${phase}`} ref={hostRef} />
<Component key={`class-${phase}`} ref={classRef} />
</>);}await act(() => {ReactNoop.render(<Example phase="mount" />);});await act(() => {ReactNoop.render(<Example phase="update" />);});});// @gate enableUseRefAccessWarningit('should warn about reads during render', async () => {function Example() {const ref = useRef(123);let value;expect(() => {value = ref.current;}).toWarnDev(['Example: Unsafe read of a mutable value during render.',]);return value;}await act(() => {ReactNoop.render(<Example />);});});it('should not warn about lazy init during render', async () => {function Example() {const ref1 = useRef(null);const ref2 = useRef(undefined);// Read: safe because lazy init:if (ref1.current === null) {ref1.current = 123;}if (ref2.current === undefined) {ref2.current = 123;}return null;}await act(() => {ReactNoop.render(<Example />);});// Should not warn after an update either.await act(() => {ReactNoop.render(<Example />);});});it('should not warn about lazy init outside of render', async () => {function Example() {// eslint-disable-next-line no-unused-varsconst [didMount, setDidMount] = useState(false);const ref1 = useRef(null);const ref2 = useRef(undefined);useLayoutEffect(() => {ref1.current = 123;ref2.current = 123;setDidMount(true);}, []);return null;}await act(() => {ReactNoop.render(<Example />);});});// @gate enableUseRefAccessWarningit('should warn about unconditional lazy init during render', async () => {function Example() {const ref1 = useRef(null);const ref2 = useRef(undefined);if (shouldExpectWarning) {expect(() => {ref1.current = 123;}).toWarnDev(['Example: Unsafe write of a mutable value during render',]);expect(() => {ref2.current = 123;}).toWarnDev(['Example: Unsafe write of a mutable value during render',]);} else {ref1.current = 123;ref1.current = 123;}// But only warn onceref1.current = 345;ref1.current = 345;return null;}let shouldExpectWarning = true;await act(() => {ReactNoop.render(<Example />);});// Should not warn again on update.shouldExpectWarning = false;await act(() => {ReactNoop.render(<Example />);});});// @gate enableUseRefAccessWarningit('should warn about reads to ref after lazy init pattern', async () => {function Example() {const ref1 = useRef(null);const ref2 = useRef(undefined);// Read 1: safe because lazy init:if (ref1.current === null) {ref1.current = 123;}if (ref2.current === undefined) {ref2.current = 123;}let value;expect(() => {value = ref1.current;}).toWarnDev(['Example: Unsafe read of a mutable value during render']);expect(() => {value = ref2.current;}).toWarnDev(['Example: Unsafe read of a mutable value during render']);// But it should only warn once.value = ref1.current;value = ref2.current;return value;}await act(() => {ReactNoop.render(<Example />);});});// @gate enableUseRefAccessWarningit('should warn about writes to ref after lazy init pattern', async () => {function Example() {const ref1 = useRef(null);const ref2 = useRef(undefined);// Read: safe because lazy init:if (ref1.current === null) {ref1.current = 123;}if (ref2.current === undefined) {ref2.current = 123;}expect(() => {ref1.current = 456;}).toWarnDev(['Example: Unsafe write of a mutable value during render',]);expect(() => {ref2.current = 456;}).toWarnDev(['Example: Unsafe write of a mutable value during render',]);return null;}await act(() => {ReactNoop.render(<Example />);});});it('should not warn about reads or writes within effect', async () => {function Example() {const ref = useRef(123);useLayoutEffect(() => {expect(ref.current).toBe(123);ref.current = 456;expect(ref.current).toBe(456);}, []);useEffect(() => {expect(ref.current).toBe(456);ref.current = 789;expect(ref.current).toBe(789);}, []);return null;}await act(() => {ReactNoop.render(<Example />);});ReactNoop.flushPassiveEffects();});it('should not warn about reads or writes outside of render phase (e.g. event handler)', async () => {let ref;function Example() {ref = useRef(123);return null;}await act(() => {ReactNoop.render(<Example />);});expect(ref.current).toBe(123);ref.current = 456;expect(ref.current).toBe(456);});}});