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');
+ },
+ );
});