@@ -1248,11 +1248,11 @@ export class RouterCore<
12481248 }
12491249
12501250 emit : EmitFn = ( routerEvent ) => {
1251- this . subscribers . forEach ( ( listener ) => {
1251+ for ( const listener of this . subscribers ) {
12521252 if ( listener . eventType === routerEvent . type ) {
12531253 listener . fn ( routerEvent )
12541254 }
1255- } )
1255+ }
12561256 }
12571257
12581258 /**
@@ -1765,24 +1765,24 @@ export class RouterCore<
17651765 }
17661766
17671767 cancelMatches = ( ) => {
1768- this . stores . pendingMatchesId . state . forEach ( ( matchId ) => {
1768+ for ( const matchId of this . stores . pendingMatchesId . state ) {
17691769 this . cancelMatch ( matchId )
1770- } )
1770+ }
17711771
1772- this . stores . matchesId . state . forEach ( ( matchId ) => {
1772+ for ( const matchId of this . stores . matchesId . state ) {
17731773 if ( this . stores . pendingMatchStoresById . has ( matchId ) ) {
1774- return
1774+ continue
17751775 }
17761776
17771777 const match = this . stores . activeMatchStoresById . get ( matchId ) ?. state
17781778 if ( ! match ) {
1779- return
1779+ continue
17801780 }
17811781
17821782 if ( match . status === 'pending' || match . isFetching === 'loader' ) {
17831783 this . cancelMatch ( matchId )
17841784 }
1785- } )
1785+ }
17861786 }
17871787
17881788 /**
@@ -1915,7 +1915,7 @@ export class RouterCore<
19151915 let nextSearch = fromSearch
19161916 if ( opts . _includeValidateSearch && this . options . search ?. strict ) {
19171917 const validatedSearch = { }
1918- destRoutes . forEach ( ( route ) => {
1918+ for ( const route of destRoutes ) {
19191919 if ( route . options . validateSearch ) {
19201920 try {
19211921 Object . assign (
@@ -1929,7 +1929,7 @@ export class RouterCore<
19291929 // ignore errors here because they are already handled in matchRoutes
19301930 }
19311931 }
1932- } )
1932+ }
19331933 nextSearch = validatedSearch
19341934 }
19351935
@@ -2093,13 +2093,13 @@ export class RouterCore<
20932093 '__TSR_index' ,
20942094 '__hashScrollIntoViewOptions' ,
20952095 ] as const
2096- ignoredProps . forEach ( ( prop ) => {
2096+ for ( const prop of ignoredProps ) {
20972097 ; ( next . state as any ) [ prop ] = this . latestLocation . state [ prop ]
2098- } )
2098+ }
20992099 const isEqual = deepEqual ( next . state , this . latestLocation . state )
2100- ignoredProps . forEach ( ( prop ) => {
2100+ for ( const prop of ignoredProps ) {
21012101 delete next . state [ prop ]
2102- } )
2102+ }
21032103 return isEqual
21042104 }
21052105
@@ -2418,54 +2418,42 @@ export class RouterCore<
24182418 // exitingMatches uses match.id (routeId + params + loaderDeps) so
24192419 // navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
24202420 let exitingMatches : Array < AnyRouteMatch > | null = null
2421-
2422- // Lifecycle-hook identity uses routeId only so that navigating between
2423- // different params/deps of the same route fires onStay (not onLeave+onEnter).
2424- let hookExitingMatches : Array < AnyRouteMatch > | null = null
2425- let hookEnteringMatches : Array < AnyRouteMatch > | null = null
2426- let hookStayingMatches : Array < AnyRouteMatch > | null = null
2421+ let pendingMatches : Array < AnyRouteMatch > = [ ]
2422+ let currentMatches : Array < AnyRouteMatch > = [ ]
2423+ let pendingRouteIds : Set < string > | null = null
2424+ let activeRouteIds : Set < string > | null = null
24272425
24282426 this . batch ( ( ) => {
2429- const pendingMatches =
2430- this . stores . pendingMatchesSnapshot . state
2431- const mountPending = pendingMatches . length
2432- const currentMatches =
2433- this . stores . activeMatchesSnapshot . state
2434-
2435- exitingMatches = mountPending
2436- ? currentMatches . filter (
2437- ( match ) =>
2438- ! this . stores . pendingMatchStoresById . has ( match . id ) ,
2439- )
2440- : null
2441-
2442- // Lifecycle-hook identity: routeId only (route presence in tree)
2443- // Build routeId sets from pools to avoid derived stores.
2444- const pendingRouteIds = new Set < string > ( )
2445- for ( const s of this . stores . pendingMatchStoresById . values ( ) ) {
2446- if ( s . routeId ) pendingRouteIds . add ( s . routeId )
2447- }
2448- const activeRouteIds = new Set < string > ( )
2449- for ( const s of this . stores . activeMatchStoresById . values ( ) ) {
2450- if ( s . routeId ) activeRouteIds . add ( s . routeId )
2427+ pendingMatches = this . stores . pendingMatchesSnapshot . state
2428+ currentMatches = this . stores . activeMatchesSnapshot . state
2429+
2430+ if ( pendingMatches . length ) {
2431+ activeRouteIds = new Set < string > ( )
2432+ for ( const match of currentMatches ) {
2433+ activeRouteIds . add ( match . routeId )
2434+ }
2435+
2436+ pendingRouteIds = new Set < string > ( )
2437+ exitingMatches = [ ]
2438+
2439+ for ( const match of pendingMatches ) {
2440+ pendingRouteIds . add ( match . routeId )
2441+ }
2442+
2443+ for ( const match of currentMatches ) {
2444+ if (
2445+ ! this . stores . pendingMatchStoresById . has ( match . id ) &&
2446+ match . status !== 'error' &&
2447+ match . status !== 'notFound' &&
2448+ match . status !== 'redirected'
2449+ ) {
2450+ exitingMatches . push ( match )
2451+ }
2452+ }
2453+ } else {
2454+ exitingMatches = null
24512455 }
24522456
2453- hookExitingMatches = mountPending
2454- ? currentMatches . filter (
2455- ( match ) => ! pendingRouteIds . has ( match . routeId ) ,
2456- )
2457- : null
2458- hookEnteringMatches = mountPending
2459- ? pendingMatches . filter (
2460- ( match ) => ! activeRouteIds . has ( match . routeId ) ,
2461- )
2462- : null
2463- hookStayingMatches = mountPending
2464- ? pendingMatches . filter ( ( match ) =>
2465- activeRouteIds . has ( match . routeId ) ,
2466- )
2467- : currentMatches
2468-
24692457 this . stores . isLoading . setState ( ( ) => false )
24702458 this . stores . loadedAt . setState ( ( ) => Date . now ( ) )
24712459 /**
@@ -2474,31 +2462,45 @@ export class RouterCore<
24742462 * deliberately excluded from `cachedMatches` so that subsequent invalidations
24752463 * or reloads re-run their loaders instead of reusing the failed/not-found data.
24762464 */
2477- if ( mountPending ) {
2465+ if ( pendingMatches . length ) {
24782466 this . stores . setActiveMatches ( pendingMatches )
24792467 this . stores . setPendingMatches ( [ ] )
24802468 this . stores . setCachedMatches ( [
24812469 ...this . stores . cachedMatchesSnapshot . state ,
2482- ...exitingMatches ! . filter (
2483- ( d ) =>
2484- d . status !== 'error' &&
2485- d . status !== 'notFound' &&
2486- d . status !== 'redirected' ,
2487- ) ,
2470+ ...exitingMatches ! ,
24882471 ] )
24892472 this . clearExpiredCache ( )
24902473 }
24912474 } )
24922475
2493- //
2494- for ( const [ matches , hook ] of [
2495- [ hookExitingMatches , 'onLeave' ] ,
2496- [ hookEnteringMatches , 'onEnter' ] ,
2497- [ hookStayingMatches , 'onStay' ] ,
2498- ] as const ) {
2499- if ( ! matches ) continue
2500- for ( const match of matches as Array < AnyRouteMatch > ) {
2501- this . looseRoutesById [ match . routeId ] ! . options [ hook ] ?.(
2476+ // Lifecycle-hook identity uses routeId only so that navigating between
2477+ // different params/deps of the same route fires onStay (not onLeave+onEnter).
2478+ const nextPendingRouteIds = pendingRouteIds as Set < string > | null
2479+ const nextActiveRouteIds = activeRouteIds as Set < string > | null
2480+
2481+ if ( nextPendingRouteIds && nextActiveRouteIds ) {
2482+ for ( const match of currentMatches ) {
2483+ if ( ! nextPendingRouteIds . has ( match . routeId ) ) {
2484+ this . looseRoutesById [ match . routeId ] ! . options . onLeave ?.(
2485+ match ,
2486+ )
2487+ }
2488+ }
2489+
2490+ for ( const match of pendingMatches ) {
2491+ if ( nextActiveRouteIds . has ( match . routeId ) ) {
2492+ this . looseRoutesById [ match . routeId ] ! . options . onStay ?.(
2493+ match ,
2494+ )
2495+ } else {
2496+ this . looseRoutesById [ match . routeId ] ! . options . onEnter ?.(
2497+ match ,
2498+ )
2499+ }
2500+ }
2501+ } else {
2502+ for ( const match of currentMatches ) {
2503+ this . looseRoutesById [ match . routeId ] ! . options . onStay ?.(
25022504 match ,
25032505 )
25042506 }
@@ -2525,9 +2527,7 @@ export class RouterCore<
25252527 ? redirect . status
25262528 : notFound
25272529 ? 404
2528- : this . stores . activeMatchesSnapshot . state . some (
2529- ( d ) => d . status === 'error' ,
2530- )
2530+ : this . hasErrorMatch ( )
25312531 ? 500
25322532 : 200
25332533
@@ -2561,9 +2561,7 @@ export class RouterCore<
25612561 let newStatusCode : number | undefined = undefined
25622562 if ( this . hasNotFoundMatch ( ) ) {
25632563 newStatusCode = 404
2564- } else if (
2565- this . stores . activeMatchesSnapshot . state . some ( ( d ) => d . status === 'error' )
2566- ) {
2564+ } else if ( this . hasErrorMatch ( ) ) {
25672565 newStatusCode = 500
25682566 }
25692567 if ( newStatusCode !== undefined ) {
@@ -2930,10 +2928,24 @@ export class RouterCore<
29302928
29312929 serverSsr ?: ServerSsr
29322930
2931+ hasErrorMatch = ( ) => {
2932+ for ( const match of this . stores . activeMatchesSnapshot . state ) {
2933+ if ( match . status === 'error' ) {
2934+ return true
2935+ }
2936+ }
2937+
2938+ return false
2939+ }
2940+
29332941 hasNotFoundMatch = ( ) => {
2934- return this . stores . activeMatchesSnapshot . state . some (
2935- ( d ) => d . status === 'notFound' || d . globalNotFound ,
2936- )
2942+ for ( const match of this . stores . activeMatchesSnapshot . state ) {
2943+ if ( match . status === 'notFound' || match . globalNotFound ) {
2944+ return true
2945+ }
2946+ }
2947+
2948+ return false
29372949 }
29382950}
29392951
0 commit comments