@@ -8,6 +8,7 @@ import React, {
88 Fragment ,
99 createContext ,
1010 useState ,
11+ useRef ,
1112 useEffect ,
1213 useLayoutEffect ,
1314 memo
@@ -202,9 +203,43 @@ describe('suspense', () => {
202203 resolve ( ) . then ( assert ) . catch ( assert ) ;
203204 } ) ;
204205
205- it ( 'should reset hooks of components' , ( ) => {
206+ it ( 'should reset hooks when a component subtree suspends during mount' , async ( ) => {
207+ let resolve ;
208+ let resolved = false ;
209+ let initializations = 0 ;
210+ const promise = new Promise ( r => {
211+ resolve = ( ) => {
212+ resolved = true ;
213+ r ( ) ;
214+ return promise ;
215+ } ;
216+ } ) ;
217+
218+ function App ( ) {
219+ const [ value ] = useState ( ( ) => ++ initializations ) ;
220+ if ( ! resolved ) throw promise ;
221+ return < p > { value } </ p > ;
222+ }
223+
224+ render (
225+ < Suspense fallback = "loading" >
226+ < App />
227+ </ Suspense > ,
228+ scratch
229+ ) ;
230+ rerender ( ) ;
231+ expect ( scratch . textContent ) . to . equal ( 'loading' ) ;
232+
233+ await resolve ( ) ;
234+ rerender ( ) ;
235+ expect ( scratch . innerHTML ) . to . equal ( '<p>2</p>' ) ;
236+ expect ( initializations ) . to . equal ( 2 ) ;
237+ } ) ;
238+
239+ it ( 'should preserve hooks of mounted components' , ( ) => {
206240 /** @type {(v) => void } */
207241 let set ;
242+ let initialRef ;
208243 const LazyComp = ( { name } ) => < div > Hello from { name } </ div > ;
209244
210245 /** @type {() => Promise<void> } */
@@ -222,7 +257,10 @@ describe('suspense', () => {
222257
223258 const Parent = ( { children } ) => {
224259 const [ state , setState ] = useState ( false ) ;
260+ const ref = useRef ( { } ) ;
225261 set = setState ;
262+ if ( ! initialRef ) initialRef = ref ;
263+ else expect ( ref ) . to . equal ( initialRef ) ;
226264
227265 return (
228266 < div >
@@ -249,15 +287,21 @@ describe('suspense', () => {
249287
250288 return resolve ( ) . then ( ( ) => {
251289 rerender ( ) ;
252- expect ( scratch . innerHTML ) . to . eql ( `<div><p>hi</p></div>` ) ;
290+ expect ( scratch . innerHTML ) . to . eql (
291+ `<div><p>hi</p><div>Hello from LazyComp</div></div>`
292+ ) ;
253293 } ) ;
254294 } ) ;
255295
256- it ( 'should call effect cleanups' , ( ) => {
296+ it ( 'should call effect cleanups and setups when hiding and revealing' , async ( ) => {
257297 /** @type {(v) => void } */
258298 let set ;
299+ const effectSetupSpy = vi . fn ( ) ;
259300 const effectSpy = vi . fn ( ) ;
301+ const effectWithoutCleanupSpy = vi . fn ( ) ;
302+ const layoutEffectSetupSpy = vi . fn ( ) ;
260303 const layoutEffectSpy = vi . fn ( ) ;
304+ const layoutEffectWithoutCleanupSpy = vi . fn ( ) ;
261305 const LazyComp = ( { name } ) => < div > Hello from { name } </ div > ;
262306
263307 /** @type {() => Promise<void> } */
@@ -277,16 +321,20 @@ describe('suspense', () => {
277321 const [ state , setState ] = useState ( false ) ;
278322 set = setState ;
279323 useEffect ( ( ) => {
324+ effectSetupSpy ( ) ;
280325 return ( ) => {
281326 effectSpy ( ) ;
282327 } ;
283- } , [ ] ) ;
328+ } , [ state ] ) ;
329+ useEffect ( effectWithoutCleanupSpy , [ state ] ) ;
284330
285331 useLayoutEffect ( ( ) => {
332+ layoutEffectSetupSpy ( ) ;
286333 return ( ) => {
287334 layoutEffectSpy ( ) ;
288335 } ;
289336 } , [ ] ) ;
337+ useLayoutEffect ( layoutEffectWithoutCleanupSpy , [ ] ) ;
290338
291339 return state ? (
292340 < div > { children } </ div >
@@ -305,19 +353,29 @@ describe('suspense', () => {
305353 </ Suspense > ,
306354 scratch
307355 ) ;
356+ expect ( layoutEffectSetupSpy ) . toHaveBeenCalledOnce ( ) ;
357+ expect ( layoutEffectWithoutCleanupSpy ) . toHaveBeenCalledOnce ( ) ;
308358
309359 set ( true ) ;
310360 rerender ( ) ;
311361 expect ( scratch . innerHTML ) . to . eql ( '<div>Suspended...</div>' ) ;
362+
363+ expect ( effectSetupSpy ) . toHaveBeenCalledOnce ( ) ;
364+ expect ( effectWithoutCleanupSpy ) . toHaveBeenCalledOnce ( ) ;
312365 expect ( effectSpy ) . toHaveBeenCalledOnce ( ) ;
313366 expect ( layoutEffectSpy ) . toHaveBeenCalledOnce ( ) ;
314367
315- return resolve ( ) . then ( ( ) => {
316- rerender ( ) ;
317- expect ( effectSpy ) . toHaveBeenCalledOnce ( ) ;
318- expect ( layoutEffectSpy ) . toHaveBeenCalledOnce ( ) ;
319- expect ( scratch . innerHTML ) . to . eql ( `<div><p>hi</p></div>` ) ;
320- } ) ;
368+ await resolve ( ) ;
369+ await act ( ( ) => rerender ( ) ) ;
370+ expect ( effectSpy ) . toHaveBeenCalledOnce ( ) ;
371+ expect ( layoutEffectSpy ) . toHaveBeenCalledOnce ( ) ;
372+ expect ( effectSetupSpy ) . toHaveBeenCalledTimes ( 2 ) ;
373+ expect ( effectWithoutCleanupSpy ) . toHaveBeenCalledTimes ( 2 ) ;
374+ expect ( layoutEffectSetupSpy ) . toHaveBeenCalledTimes ( 2 ) ;
375+ expect ( layoutEffectWithoutCleanupSpy ) . toHaveBeenCalledTimes ( 2 ) ;
376+ expect ( scratch . innerHTML ) . to . eql (
377+ `<div><div>Hello from LazyComp</div></div>`
378+ ) ;
321379 } ) ;
322380
323381 it ( 'should support a call to setState before rendering the fallback' , ( ) => {
0 commit comments