diff --git a/packages/react-reconciler/src/__tests__/ReactUse-test.js b/packages/react-reconciler/src/__tests__/ReactUse-test.js index dede68854c615..7374b8f4ffdf5 100644 --- a/packages/react-reconciler/src/__tests__/ReactUse-test.js +++ b/packages/react-reconciler/src/__tests__/ReactUse-test.js @@ -16,8 +16,10 @@ let act; let use; let useDebugValue; let useState; +let useTransition; let useMemo; let useEffect; +let useOptimistic; let Suspense; let startTransition; let pendingTextRequests; @@ -38,8 +40,10 @@ describe('ReactUse', () => { use = React.use; useDebugValue = React.useDebugValue; useState = React.useState; + useTransition = React.useTransition; useMemo = React.useMemo; useEffect = React.useEffect; + useOptimistic = React.useOptimistic; Suspense = React.Suspense; startTransition = React.startTransition; @@ -1915,4 +1919,80 @@ describe('ReactUse', () => { assertLog(['Hi', 'World']); expect(root).toMatchRenderedOutput(
Hi World
); }); + + it( + 'regression: does not get stuck in pending state after `use` suspends ' + + '(when `use` comes before all hooks)', + async () => { + // This is a regression test. The root cause was an issue where we failed to + // switch from the "re-render" dispatcher back to the "update" dispatcher + // after a `use` suspends and triggers a replay. + let update; + function App({promise}) { + const value = use(promise); + + const [isPending, startLocalTransition] = useTransition(); + update = () => { + startLocalTransition(() => { + root.render(); + }); + }; + + return ; + } + + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + assertLog(['Initial']); + expect(root).toMatchRenderedOutput('Initial'); + + await act(() => update()); + assertLog(['Async text requested [Updated]', 'Initial (pending...)']); + + await act(() => resolveTextRequests('Updated')); + assertLog(['Updated']); + expect(root).toMatchRenderedOutput('Updated'); + }, + ); + + it( + 'regression: does not get stuck in pending state after `use` suspends ' + + '(when `use` in in the middle of hook list)', + async () => { + // Same as previous test but `use` comes in between two hooks. + let update; + function App({promise}) { + // This hook is only here to test that `use` resumes correctly after + // suspended even if it comes in between other hooks. + useState(false); + + const value = use(promise); + + const [isPending, startLocalTransition] = useTransition(); + update = () => { + startLocalTransition(() => { + root.render(); + }); + }; + + return ; + } + + const root = ReactNoop.createRoot(); + await act(() => { + root.render(); + }); + assertLog(['Initial']); + expect(root).toMatchRenderedOutput('Initial'); + + await act(() => update()); + assertLog(['Async text requested [Updated]', 'Initial (pending...)']); + + await act(() => resolveTextRequests('Updated')); + assertLog(['Updated']); + expect(root).toMatchRenderedOutput('Updated'); + }, + ); });