@@ -106,7 +106,7 @@ export function invariant<T>(
106
106
) : asserts value is T ;
107
107
export function invariant ( value : any , message ?: string ) {
108
108
if ( value === false || value === null || typeof value === "undefined" ) {
109
- console . warn ( "Test invaeriant failed:" , message ) ;
109
+ console . warn ( "Test invariant failed:" , message ) ;
110
110
throw new Error ( message ) ;
111
111
}
112
112
}
@@ -427,13 +427,27 @@ function setup({
427
427
opts ?: RouterNavigateOptions
428
428
) : FetcherHelpers {
429
429
let matches = matchRoutes ( enhancedRoutes , href ) ;
430
- invariant ( matches , `No matches found for fetcher href:${ href } ` ) ;
431
430
invariant ( currentRouter , "No currentRouter available" ) ;
432
431
let search = parsePath ( href ) . search || "" ;
433
432
let hasNakedIndexQuery = new URLSearchParams ( search )
434
433
. getAll ( "index" )
435
434
. some ( ( v ) => v === "" ) ;
436
435
436
+ // Let fetcher 404s go right through
437
+ if ( ! matches ) {
438
+ return {
439
+ key,
440
+ navigationId,
441
+ get fetcher ( ) {
442
+ invariant ( currentRouter , "No currentRouter available" ) ;
443
+ return currentRouter . getFetcher ( key ) ;
444
+ } ,
445
+ loaders : { } ,
446
+ actions : { } ,
447
+ shimLoaderHelper,
448
+ } ;
449
+ }
450
+
437
451
let match =
438
452
matches [ matches . length - 1 ] . route . index && ! hasNakedIndexQuery
439
453
? matches . slice ( - 2 ) [ 0 ]
@@ -560,14 +574,30 @@ function setup({
560
574
key : string ,
561
575
opts : RouterNavigateOptions
562
576
) : Promise < FetcherHelpers > ;
577
+ async function fetch (
578
+ href : string ,
579
+ key : string ,
580
+ routeId : string ,
581
+ opts : RouterNavigateOptions
582
+ ) : Promise < FetcherHelpers > ;
563
583
async function fetch (
564
584
href : string ,
565
585
keyOrOpts ?: string | RouterNavigateOptions ,
586
+ routeIdOrOpts ?: string | RouterNavigateOptions ,
566
587
opts ?: RouterNavigateOptions
567
588
) : Promise < FetcherHelpers > {
568
589
let navigationId = ++ guid ;
569
590
let key = typeof keyOrOpts === "string" ? keyOrOpts : String ( navigationId ) ;
570
- opts = typeof keyOrOpts === "object" ? keyOrOpts : opts ;
591
+ let routeId =
592
+ typeof routeIdOrOpts === "string"
593
+ ? routeIdOrOpts
594
+ : String ( enhancedRoutes [ 0 ] . id ) ;
595
+ opts =
596
+ typeof keyOrOpts === "object"
597
+ ? keyOrOpts
598
+ : typeof routeIdOrOpts === "object"
599
+ ? routeIdOrOpts
600
+ : opts ;
571
601
invariant ( currentRouter , "No currentRouter available" ) ;
572
602
573
603
// @ts -expect-error
@@ -580,7 +610,7 @@ function setup({
580
610
}
581
611
582
612
let helpers = getFetcherHelpers ( key , href , navigationId , opts ) ;
583
- currentRouter . fetch ( key , enhancedRoutes [ 0 ] . id , href , opts ) ;
613
+ currentRouter . fetch ( key , routeId , href , opts ) ;
584
614
return helpers ;
585
615
}
586
616
@@ -5557,6 +5587,93 @@ describe("a router", () => {
5557
5587
root : new ErrorResponse ( 400 , undefined , "" ) ,
5558
5588
} ) ;
5559
5589
} ) ;
5590
+
5591
+ it ( "handles fetcher errors at contextual route boundaries" , async ( ) => {
5592
+ let t = setup ( {
5593
+ routes : [
5594
+ {
5595
+ id : "root" ,
5596
+ path : "/" ,
5597
+ errorElement : true ,
5598
+ children : [
5599
+ {
5600
+ id : "wit" ,
5601
+ path : "wit" ,
5602
+ loader : true ,
5603
+ errorElement : true ,
5604
+ } ,
5605
+ {
5606
+ id : "witout" ,
5607
+ path : "witout" ,
5608
+ loader : true ,
5609
+ } ,
5610
+ {
5611
+ id : "error" ,
5612
+ path : "error" ,
5613
+ loader : true ,
5614
+ } ,
5615
+ ] ,
5616
+ } ,
5617
+ ] ,
5618
+ } ) ;
5619
+
5620
+ // If the routeId is not an active match, errors bubble to the root
5621
+ let A = await t . fetch ( "/error" , "key1" , "wit" ) ;
5622
+ await A . loaders . error . reject ( new Error ( "Kaboom!" ) ) ;
5623
+ expect ( t . router . getFetcher ( "key1" ) ) . toBe ( IDLE_FETCHER ) ;
5624
+ expect ( t . router . state . errors ) . toEqual ( {
5625
+ root : new Error ( "Kaboom!" ) ,
5626
+ } ) ;
5627
+
5628
+ await t . fetch ( "/not-found" , "key2" , "wit" ) ;
5629
+ expect ( t . router . getFetcher ( "key2" ) ) . toBe ( IDLE_FETCHER ) ;
5630
+ expect ( t . router . state . errors ) . toEqual ( {
5631
+ root : new ErrorResponse ( 404 , "Not Found" , null ) ,
5632
+ } ) ;
5633
+
5634
+ // Navigate to /wit and trigger errors, handled at the wit boundary
5635
+ let B = await t . navigate ( "/wit" ) ;
5636
+ await B . loaders . wit . resolve ( "WIT" ) ;
5637
+
5638
+ let C = await t . fetch ( "/error" , "key3" , "wit" ) ;
5639
+ await C . loaders . error . reject ( new Error ( "Kaboom!" ) ) ;
5640
+ expect ( t . router . getFetcher ( "key3" ) ) . toBe ( IDLE_FETCHER ) ;
5641
+ expect ( t . router . state . errors ) . toEqual ( {
5642
+ wit : new Error ( "Kaboom!" ) ,
5643
+ } ) ;
5644
+
5645
+ await t . fetch ( "/not-found" , "key4" , "wit" , {
5646
+ formMethod : "post" ,
5647
+ formData : createFormData ( { key : "value" } ) ,
5648
+ } ) ;
5649
+ expect ( t . router . getFetcher ( "key4" ) ) . toBe ( IDLE_FETCHER ) ;
5650
+ expect ( t . router . state . errors ) . toEqual ( {
5651
+ wit : new ErrorResponse ( 404 , "Not Found" , null ) ,
5652
+ } ) ;
5653
+
5654
+ await t . fetch ( "/not-found" , "key5" , "wit" ) ;
5655
+ expect ( t . router . getFetcher ( "key5" ) ) . toBe ( IDLE_FETCHER ) ;
5656
+ expect ( t . router . state . errors ) . toEqual ( {
5657
+ wit : new ErrorResponse ( 404 , "Not Found" , null ) ,
5658
+ } ) ;
5659
+
5660
+ // Navigate to /witout and fetch a 404, handled at the root boundary
5661
+ let D = await t . navigate ( "/witout" ) ;
5662
+ await D . loaders . witout . resolve ( "WITOUT" ) ;
5663
+
5664
+ let E = await t . fetch ( "/error" , "key6" , "witout" ) ;
5665
+ await E . loaders . error . reject ( new Error ( "Kaboom!" ) ) ;
5666
+ expect ( t . router . getFetcher ( "key6" ) ) . toBe ( IDLE_FETCHER ) ;
5667
+ expect ( t . router . state . errors ) . toEqual ( {
5668
+ root : new Error ( "Kaboom!" ) ,
5669
+ } ) ;
5670
+
5671
+ await t . fetch ( "/not-found" , "key7" , "witout" ) ;
5672
+ expect ( t . router . getFetcher ( "key7" ) ) . toBe ( IDLE_FETCHER ) ;
5673
+ expect ( t . router . state . errors ) . toEqual ( {
5674
+ root : new ErrorResponse ( 404 , "Not Found" , null ) ,
5675
+ } ) ;
5676
+ } ) ;
5560
5677
} ) ;
5561
5678
5562
5679
describe ( "fetcher error states (Error)" , ( ) => {
0 commit comments