'use strict';
let React;
let ReactNoop;
let Scheduler;
let act;
let startTransition;
let useDeferredValue;
let useMemo;
let useState;
let assertLog;
let waitForPaint;
describe('ReactDeferredValue', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
startTransition = React.startTransition;
useDeferredValue = React.useDeferredValue;
useMemo = React.useMemo;
useState = React.useState;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitForPaint = InternalTestUtils.waitForPaint;
});
function Text({text}) {
Scheduler.log(text);
return text;
}
it('does not cause an infinite defer loop if the original value isn\t memoized', async () => {
function App({value}) {
const {value: deferredValue} = useDeferredValue({value});
const child = useMemo(
() => <Text text={'Original: ' + value} />,
[value],
);
const deferredChild = useMemo(
() => <Text text={'Deferred: ' + deferredValue} />,
[deferredValue],
);
return (
<div>
<div>{child}</div>
<div>{deferredChild}</div>
</div>
);
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(<App value={1} />);
});
assertLog(['Original: 1', 'Deferred: 1']);
await act(async () => {
root.render(<App value={2} />);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 2</div>
<div>Deferred: 2</div>
</div>,
);
await act(async () => {
startTransition(() => {
root.render(<App value={3} />);
});
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 3</div>
</div>,
);
});
it('does not defer during a transition', async () => {
function App({value}) {
const deferredValue = useDeferredValue(value);
const child = useMemo(
() => <Text text={'Original: ' + value} />,
[value],
);
const deferredChild = useMemo(
() => <Text text={'Deferred: ' + deferredValue} />,
[deferredValue],
);
return (
<div>
<div>{child}</div>
<div>{deferredChild}</div>
</div>
);
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(<App value={1} />);
});
assertLog(['Original: 1', 'Deferred: 1']);
await act(async () => {
root.render(<App value={2} />);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 2</div>
<div>Deferred: 2</div>
</div>,
);
await act(async () => {
startTransition(() => {
root.render(<App value={3} />);
});
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 3</div>
</div>,
);
});
it("works if there's a render phase update", async () => {
function App({value: propValue}) {
const [value, setValue] = useState(null);
if (value !== propValue) {
setValue(propValue);
}
const deferredValue = useDeferredValue(value);
const child = useMemo(
() => <Text text={'Original: ' + value} />,
[value],
);
const deferredChild = useMemo(
() => <Text text={'Deferred: ' + deferredValue} />,
[deferredValue],
);
return (
<div>
<div>{child}</div>
<div>{deferredChild}</div>
</div>
);
}
const root = ReactNoop.createRoot();
await act(() => {
root.render(<App value={1} />);
});
assertLog(['Original: 1', 'Deferred: 1']);
await act(async () => {
root.render(<App value={2} />);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 2</div>
<div>Deferred: 2</div>
</div>,
);
await act(async () => {
startTransition(() => {
root.render(<App value={3} />);
});
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 3</div>
</div>,
);
});
it('regression test: during urgent update, reuse previous value, not initial value', async () => {
function App({value: propValue}) {
const [value, setValue] = useState(null);
if (value !== propValue) {
setValue(propValue);
}
const deferredValue = useDeferredValue(value);
const child = useMemo(
() => <Text text={'Original: ' + value} />,
[value],
);
const deferredChild = useMemo(
() => <Text text={'Deferred: ' + deferredValue} />,
[deferredValue],
);
return (
<div>
<div>{child}</div>
<div>{deferredChild}</div>
</div>
);
}
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App value={1} />);
await waitForPaint(['Original: 1', 'Deferred: 1']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 1</div>
<div>Deferred: 1</div>
</div>,
);
});
await act(async () => {
startTransition(() => {
root.render(<App value={2} />);
});
await waitForPaint(['Original: 2', 'Deferred: 2']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 2</div>
<div>Deferred: 2</div>
</div>,
);
});
await act(async () => {
root.render(<App value={3} />);
await waitForPaint(['Original: 3']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 2</div>
</div>,
);
await waitForPaint(['Deferred: 3']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 3</div>
</div>,
);
});
});
});