@@ -3758,4 +3758,119 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3758
3758
</ > ,
3759
3759
) ;
3760
3760
} ) ;
3761
+
3762
+ // Regression: https://github.com/facebook/react/issues/18486
3763
+ it . experimental (
3764
+ 'does not get stuck in pending state with render phase updates' ,
3765
+ async ( ) => {
3766
+ let setTextWithTransition ;
3767
+
3768
+ function App ( ) {
3769
+ const [ startTransition , isPending ] = React . useTransition ( {
3770
+ timeoutMs : 30000 ,
3771
+ } ) ;
3772
+ const [ text , setText ] = React . useState ( '' ) ;
3773
+ const [ mirror , setMirror ] = React . useState ( '' ) ;
3774
+
3775
+ if ( text !== mirror ) {
3776
+ // Render phase update was needed to repro the bug.
3777
+ setMirror ( text ) ;
3778
+ }
3779
+
3780
+ setTextWithTransition = value => {
3781
+ startTransition ( ( ) => {
3782
+ setText ( value ) ;
3783
+ } ) ;
3784
+ } ;
3785
+
3786
+ return (
3787
+ < >
3788
+ { isPending ? < Text text = "Pending..." /> : null }
3789
+ { text !== '' ? < AsyncText text = { text } /> : < Text text = { text } /> }
3790
+ </ >
3791
+ ) ;
3792
+ }
3793
+
3794
+ function Root ( ) {
3795
+ return (
3796
+ < Suspense fallback = { < Text text = "Loading..." /> } >
3797
+ < App />
3798
+ </ Suspense >
3799
+ ) ;
3800
+ }
3801
+
3802
+ const root = ReactNoop . createRoot ( ) ;
3803
+ await ReactNoop . act ( async ( ) => {
3804
+ root . render ( < Root /> ) ;
3805
+ } ) ;
3806
+ expect ( Scheduler ) . toHaveYielded ( [ '' ] ) ;
3807
+ expect ( root ) . toMatchRenderedOutput ( < span prop = "" /> ) ;
3808
+
3809
+ // Update to "a". That will suspend.
3810
+ await ReactNoop . act ( async ( ) => {
3811
+ setTextWithTransition ( 'a' ) ;
3812
+ // Let it expire. This is important for the repro.
3813
+ Scheduler . unstable_advanceTime ( 1000 ) ;
3814
+ expect ( Scheduler ) . toFlushAndYield ( [
3815
+ 'Pending...' ,
3816
+ '' ,
3817
+ 'Suspend! [a]' ,
3818
+ 'Loading...' ,
3819
+ ] ) ;
3820
+ } ) ;
3821
+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
3822
+ expect ( root ) . toMatchRenderedOutput (
3823
+ < >
3824
+ < span prop = "Pending..." />
3825
+ < span prop = "" />
3826
+ </ > ,
3827
+ ) ;
3828
+
3829
+ // Update to "b". That will suspend, too.
3830
+ await ReactNoop . act ( async ( ) => {
3831
+ setTextWithTransition ( 'b' ) ;
3832
+ expect ( Scheduler ) . toFlushAndYield ( [
3833
+ // Neither is resolved yet.
3834
+ 'Pending...' ,
3835
+ 'Suspend! [a]' ,
3836
+ 'Loading...' ,
3837
+ 'Suspend! [b]' ,
3838
+ 'Loading...' ,
3839
+ ] ) ;
3840
+ } ) ;
3841
+ expect ( Scheduler ) . toHaveYielded ( [ ] ) ;
3842
+ expect ( root ) . toMatchRenderedOutput (
3843
+ < >
3844
+ < span prop = "Pending..." />
3845
+ < span prop = "" />
3846
+ </ > ,
3847
+ ) ;
3848
+
3849
+ // Resolve "a". But "b" is still pending.
3850
+ await ReactNoop . act ( async ( ) => {
3851
+ await resolveText ( 'a' ) ;
3852
+ } ) ;
3853
+ expect ( Scheduler ) . toHaveYielded ( [
3854
+ 'Promise resolved [a]' ,
3855
+ 'Pending...' ,
3856
+ 'a' ,
3857
+ 'Suspend! [b]' ,
3858
+ 'Loading...' ,
3859
+ ] ) ;
3860
+ expect ( root ) . toMatchRenderedOutput (
3861
+ < >
3862
+ < span prop = "Pending..." />
3863
+ < span prop = "a" />
3864
+ </ > ,
3865
+ ) ;
3866
+
3867
+ // Resolve "b". This should remove the pending state.
3868
+ await ReactNoop . act ( async ( ) => {
3869
+ await resolveText ( 'b' ) ;
3870
+ } ) ;
3871
+ expect ( Scheduler ) . toHaveYielded ( [ 'Promise resolved [b]' , 'b' ] ) ;
3872
+ // The bug was that the pending state got stuck forever.
3873
+ expect ( root ) . toMatchRenderedOutput ( < span prop = "b" /> ) ;
3874
+ } ,
3875
+ ) ;
3761
3876
} ) ;
0 commit comments