@@ -301,6 +301,211 @@ describe("useNavigate", () => {
301
301
) ;
302
302
} ) ;
303
303
304
+ describe ( "navigating in effects versus render" , ( ) => {
305
+ let warnSpy : jest . SpyInstance ;
306
+
307
+ beforeEach ( ( ) => {
308
+ warnSpy = jest . spyOn ( console , "warn" ) . mockImplementation ( ( ) => { } ) ;
309
+ } ) ;
310
+
311
+ afterEach ( ( ) => {
312
+ warnSpy . mockRestore ( ) ;
313
+ } ) ;
314
+
315
+ describe ( "MemoryRouter" , ( ) => {
316
+ it ( "does not allow navigation from the render cycle" , ( ) => {
317
+ let renderer : TestRenderer . ReactTestRenderer ;
318
+ TestRenderer . act ( ( ) => {
319
+ renderer = TestRenderer . create (
320
+ < MemoryRouter >
321
+ < Routes >
322
+ < Route index element = { < Home /> } />
323
+ < Route path = "about" element = { < h1 > About</ h1 > } />
324
+ </ Routes >
325
+ </ MemoryRouter >
326
+ ) ;
327
+ } ) ;
328
+
329
+ function Home ( ) {
330
+ let navigate = useNavigate ( ) ;
331
+ navigate ( "/about" ) ;
332
+ return < h1 > Home</ h1 > ;
333
+ }
334
+
335
+ // @ts -expect-error
336
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
337
+ <h1>
338
+ Home
339
+ </h1>
340
+ ` ) ;
341
+ expect ( warnSpy ) . toHaveBeenCalledWith (
342
+ "You should call navigate() in a React.useEffect(), not when your component is first rendered."
343
+ ) ;
344
+ } ) ;
345
+
346
+ it ( "allows navigation from effects" , ( ) => {
347
+ let renderer : TestRenderer . ReactTestRenderer ;
348
+ TestRenderer . act ( ( ) => {
349
+ renderer = TestRenderer . create (
350
+ < MemoryRouter >
351
+ < Routes >
352
+ < Route index element = { < Home /> } />
353
+ < Route path = "about" element = { < h1 > About</ h1 > } />
354
+ </ Routes >
355
+ </ MemoryRouter >
356
+ ) ;
357
+ } ) ;
358
+
359
+ function Home ( ) {
360
+ let navigate = useNavigate ( ) ;
361
+ React . useEffect ( ( ) => navigate ( "/about" ) , [ navigate ] ) ;
362
+ return < h1 > Home</ h1 > ;
363
+ }
364
+
365
+ // @ts -expect-error
366
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
367
+ <h1>
368
+ About
369
+ </h1>
370
+ ` ) ;
371
+ expect ( warnSpy ) . not . toHaveBeenCalled ( ) ;
372
+ } ) ;
373
+
374
+ it ( "allows navigation in child useEffects" , ( ) => {
375
+ let renderer : TestRenderer . ReactTestRenderer ;
376
+ TestRenderer . act ( ( ) => {
377
+ renderer = TestRenderer . create (
378
+ < MemoryRouter initialEntries = { [ "/home" ] } >
379
+ < Routes >
380
+ < Route path = "home" element = { < Parent /> } />
381
+ < Route path = "about" element = { < h1 > About</ h1 > } />
382
+ </ Routes >
383
+ </ MemoryRouter >
384
+ ) ;
385
+ } ) ;
386
+
387
+ function Parent ( ) {
388
+ let navigate = useNavigate ( ) ;
389
+ let onChildRendered = React . useCallback (
390
+ ( ) => navigate ( "/about" ) ,
391
+ [ navigate ]
392
+ ) ;
393
+ return < Child onChildRendered = { onChildRendered } /> ;
394
+ }
395
+
396
+ function Child ( { onChildRendered } ) {
397
+ React . useEffect ( ( ) => onChildRendered ( ) ) ;
398
+ return null ;
399
+ }
400
+
401
+ // @ts -expect-error
402
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
403
+ <h1>
404
+ About
405
+ </h1>
406
+ ` ) ;
407
+ } ) ;
408
+ } ) ;
409
+
410
+ describe ( "RouterProvider" , ( ) => {
411
+ it ( "does not allow navigation from the render cycle" , async ( ) => {
412
+ let router = createMemoryRouter ( [
413
+ {
414
+ index : true ,
415
+ Component ( ) {
416
+ let navigate = useNavigate ( ) ;
417
+ navigate ( "/about" ) ;
418
+ return < h1 > Home</ h1 > ;
419
+ } ,
420
+ } ,
421
+ {
422
+ path : "about" ,
423
+ element : < h1 > About</ h1 > ,
424
+ } ,
425
+ ] ) ;
426
+ let renderer : TestRenderer . ReactTestRenderer ;
427
+ TestRenderer . act ( ( ) => {
428
+ renderer = TestRenderer . create ( < RouterProvider router = { router } /> ) ;
429
+ } ) ;
430
+
431
+ // @ts -expect-error
432
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
433
+ <h1>
434
+ Home
435
+ </h1>
436
+ ` ) ;
437
+ expect ( warnSpy ) . toHaveBeenCalledWith (
438
+ "You should call navigate() in a React.useEffect(), not when your component is first rendered."
439
+ ) ;
440
+ } ) ;
441
+
442
+ it ( "allows navigation from effects" , ( ) => {
443
+ let router = createMemoryRouter ( [
444
+ {
445
+ index : true ,
446
+ Component ( ) {
447
+ let navigate = useNavigate ( ) ;
448
+ React . useEffect ( ( ) => navigate ( "/about" ) , [ navigate ] ) ;
449
+ return < h1 > Home</ h1 > ;
450
+ } ,
451
+ } ,
452
+ {
453
+ path : "about" ,
454
+ element : < h1 > About</ h1 > ,
455
+ } ,
456
+ ] ) ;
457
+ let renderer : TestRenderer . ReactTestRenderer ;
458
+ TestRenderer . act ( ( ) => {
459
+ renderer = TestRenderer . create ( < RouterProvider router = { router } /> ) ;
460
+ } ) ;
461
+
462
+ // @ts -expect-error
463
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
464
+ <h1>
465
+ About
466
+ </h1>
467
+ ` ) ;
468
+ expect ( warnSpy ) . not . toHaveBeenCalled ( ) ;
469
+ } ) ;
470
+
471
+ it ( "allows navigation in child useEffects" , ( ) => {
472
+ let router = createMemoryRouter ( [
473
+ {
474
+ index : true ,
475
+ Component ( ) {
476
+ let navigate = useNavigate ( ) ;
477
+ let onChildRendered = React . useCallback (
478
+ ( ) => navigate ( "/about" ) ,
479
+ [ navigate ]
480
+ ) ;
481
+ return < Child onChildRendered = { onChildRendered } /> ;
482
+ } ,
483
+ } ,
484
+ {
485
+ path : "about" ,
486
+ element : < h1 > About</ h1 > ,
487
+ } ,
488
+ ] ) ;
489
+ let renderer : TestRenderer . ReactTestRenderer ;
490
+ TestRenderer . act ( ( ) => {
491
+ renderer = TestRenderer . create ( < RouterProvider router = { router } /> ) ;
492
+ } ) ;
493
+
494
+ function Child ( { onChildRendered } ) {
495
+ React . useEffect ( ( ) => onChildRendered ( ) ) ;
496
+ return null ;
497
+ }
498
+
499
+ // @ts -expect-error
500
+ expect ( renderer . toJSON ( ) ) . toMatchInlineSnapshot ( `
501
+ <h1>
502
+ About
503
+ </h1>
504
+ ` ) ;
505
+ } ) ;
506
+ } ) ;
507
+ } ) ;
508
+
304
509
describe ( "with state" , ( ) => {
305
510
it ( "adds the state to location.state" , ( ) => {
306
511
function Home ( ) {
0 commit comments