@@ -16,8 +16,10 @@ let act;
16
16
let use ;
17
17
let useDebugValue ;
18
18
let useState ;
19
+ let useTransition ;
19
20
let useMemo ;
20
21
let useEffect ;
22
+ let useOptimistic ;
21
23
let Suspense ;
22
24
let startTransition ;
23
25
let pendingTextRequests ;
@@ -38,8 +40,10 @@ describe('ReactUse', () => {
38
40
use = React . use ;
39
41
useDebugValue = React . useDebugValue ;
40
42
useState = React . useState ;
43
+ useTransition = React . useTransition ;
41
44
useMemo = React . useMemo ;
42
45
useEffect = React . useEffect ;
46
+ useOptimistic = React . useOptimistic ;
43
47
Suspense = React . Suspense ;
44
48
startTransition = React . startTransition ;
45
49
@@ -1915,4 +1919,80 @@ describe('ReactUse', () => {
1915
1919
assertLog ( [ 'Hi' , 'World' ] ) ;
1916
1920
expect ( root ) . toMatchRenderedOutput ( < div > Hi World</ div > ) ;
1917
1921
} ) ;
1922
+
1923
+ it (
1924
+ 'regression: does not get stuck in pending state after `use` suspends ' +
1925
+ '(when `use` comes before all hooks)' ,
1926
+ async ( ) => {
1927
+ // This is a regression test. The root cause was an issue where we failed to
1928
+ // switch from the "re-render" dispatcher back to the "update" dispatcher
1929
+ // after a `use` suspends and triggers a replay.
1930
+ let update ;
1931
+ function App ( { promise} ) {
1932
+ const value = use ( promise ) ;
1933
+
1934
+ const [ isPending , startLocalTransition ] = useTransition ( ) ;
1935
+ update = ( ) => {
1936
+ startLocalTransition ( ( ) => {
1937
+ root . render ( < App promise = { getAsyncText ( 'Updated' ) } /> ) ;
1938
+ } ) ;
1939
+ } ;
1940
+
1941
+ return < Text text = { value + ( isPending ? ' (pending...)' : '' ) } /> ;
1942
+ }
1943
+
1944
+ const root = ReactNoop . createRoot ( ) ;
1945
+ await act ( ( ) => {
1946
+ root . render ( < App promise = { Promise . resolve ( 'Initial' ) } /> ) ;
1947
+ } ) ;
1948
+ assertLog ( [ 'Initial' ] ) ;
1949
+ expect ( root ) . toMatchRenderedOutput ( 'Initial' ) ;
1950
+
1951
+ await act ( ( ) => update ( ) ) ;
1952
+ assertLog ( [ 'Async text requested [Updated]' , 'Initial (pending...)' ] ) ;
1953
+
1954
+ await act ( ( ) => resolveTextRequests ( 'Updated' ) ) ;
1955
+ assertLog ( [ 'Updated' ] ) ;
1956
+ expect ( root ) . toMatchRenderedOutput ( 'Updated' ) ;
1957
+ } ,
1958
+ ) ;
1959
+
1960
+ it (
1961
+ 'regression: does not get stuck in pending state after `use` suspends ' +
1962
+ '(when `use` in in the middle of hook list)' ,
1963
+ async ( ) => {
1964
+ // Same as previous test but `use` comes in between two hooks.
1965
+ let update ;
1966
+ function App ( { promise} ) {
1967
+ // This hook is only here to test that `use` resumes correctly after
1968
+ // suspended even if it comes in between other hooks.
1969
+ useState ( false ) ;
1970
+
1971
+ const value = use ( promise ) ;
1972
+
1973
+ const [ isPending , startLocalTransition ] = useTransition ( ) ;
1974
+ update = ( ) => {
1975
+ startLocalTransition ( ( ) => {
1976
+ root . render ( < App promise = { getAsyncText ( 'Updated' ) } /> ) ;
1977
+ } ) ;
1978
+ } ;
1979
+
1980
+ return < Text text = { value + ( isPending ? ' (pending...)' : '' ) } /> ;
1981
+ }
1982
+
1983
+ const root = ReactNoop . createRoot ( ) ;
1984
+ await act ( ( ) => {
1985
+ root . render ( < App promise = { Promise . resolve ( 'Initial' ) } /> ) ;
1986
+ } ) ;
1987
+ assertLog ( [ 'Initial' ] ) ;
1988
+ expect ( root ) . toMatchRenderedOutput ( 'Initial' ) ;
1989
+
1990
+ await act ( ( ) => update ( ) ) ;
1991
+ assertLog ( [ 'Async text requested [Updated]' , 'Initial (pending...)' ] ) ;
1992
+
1993
+ await act ( ( ) => resolveTextRequests ( 'Updated' ) ) ;
1994
+ assertLog ( [ 'Updated' ] ) ;
1995
+ expect ( root ) . toMatchRenderedOutput ( 'Updated' ) ;
1996
+ } ,
1997
+ ) ;
1918
1998
} ) ;
0 commit comments