@@ -1481,6 +1481,7 @@ export function createRouter(init: RouterInit): Router {
14811481 cancelledDeferredRoutes ,
14821482 cancelledFetcherLoads ,
14831483 fetchLoadMatches ,
1484+ fetchRedirectIds ,
14841485 routesToUse ,
14851486 basename ,
14861487 pendingActionData ,
@@ -1539,6 +1540,9 @@ export function createRouter(init: RouterInit): Router {
15391540
15401541 pendingNavigationLoadId = ++ incrementingLoadId ;
15411542 revalidatingFetchers . forEach ( ( rf ) => {
1543+ if ( fetchControllers . has ( rf . key ) ) {
1544+ abortFetcher ( rf . key ) ;
1545+ }
15421546 if ( rf . controller ) {
15431547 // Fetchers use an independent AbortController so that aborting a fetcher
15441548 // (via deleteFetcher) does not abort the triggering navigation that
@@ -1806,6 +1810,7 @@ export function createRouter(init: RouterInit): Router {
18061810 cancelledDeferredRoutes ,
18071811 cancelledFetcherLoads ,
18081812 fetchLoadMatches ,
1813+ fetchRedirectIds ,
18091814 routesToUse ,
18101815 basename ,
18111816 { [ match . route . id ] : actionResult . data } ,
@@ -1825,6 +1830,9 @@ export function createRouter(init: RouterInit): Router {
18251830 existingFetcher ? existingFetcher . data : undefined
18261831 ) ;
18271832 state . fetchers . set ( staleKey , revalidatingFetcher ) ;
1833+ if ( fetchControllers . has ( staleKey ) ) {
1834+ abortFetcher ( staleKey ) ;
1835+ }
18281836 if ( rf . controller ) {
18291837 fetchControllers . set ( staleKey , rf . controller ) ;
18301838 }
@@ -3276,6 +3284,7 @@ function getMatchesToLoad(
32763284 cancelledDeferredRoutes : string [ ] ,
32773285 cancelledFetcherLoads : string [ ] ,
32783286 fetchLoadMatches : Map < string , FetchLoadMatch > ,
3287+ fetchRedirectIds : Set < string > ,
32793288 routesToUse : AgnosticDataRouteObject [ ] ,
32803289 basename : string | undefined ,
32813290 pendingActionData ?: RouteData ,
@@ -3361,34 +3370,38 @@ function getMatchesToLoad(
33613370 return ;
33623371 }
33633372
3373+ // Revalidating fetchers are decoupled from the route matches since they
3374+ // load from a static href. They only set `defaultShouldRevalidate` on
3375+ // explicit revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3376+ //
3377+ // They automatically revalidate without even calling shouldRevalidate if:
3378+ // - They were cancelled
3379+ // - They're in the middle of their first load and therefore this is still
3380+ // an initial load and not a revalidation
3381+ //
3382+ // If neither of those is true, then they _always_ check shouldRevalidate
3383+ let fetcher = state . fetchers . get ( key ) ;
3384+ let isPerformingInitialLoad =
3385+ fetcher &&
3386+ fetcher . state !== "idle" &&
3387+ fetcher . data === undefined &&
3388+ // If a fetcher.load redirected then it'll be "loading" without any data
3389+ // so ensure we're not processing the redirect from this fetcher
3390+ ! fetchRedirectIds . has ( key ) ;
33643391 let fetcherMatch = getTargetMatch ( fetcherMatches , f . path ) ;
3365-
3366- if ( cancelledFetcherLoads . includes ( key ) ) {
3367- revalidatingFetchers . push ( {
3368- key,
3369- routeId : f . routeId ,
3370- path : f . path ,
3371- matches : fetcherMatches ,
3372- match : fetcherMatch ,
3373- controller : new AbortController ( ) ,
3392+ let shouldRevalidate =
3393+ cancelledFetcherLoads . includes ( key ) ||
3394+ isPerformingInitialLoad ||
3395+ shouldRevalidateLoader ( fetcherMatch , {
3396+ currentUrl,
3397+ currentParams : state . matches [ state . matches . length - 1 ] . params ,
3398+ nextUrl,
3399+ nextParams : matches [ matches . length - 1 ] . params ,
3400+ ...submission ,
3401+ actionResult,
3402+ defaultShouldRevalidate : isRevalidationRequired ,
33743403 } ) ;
3375- return ;
3376- }
33773404
3378- // Revalidating fetchers are decoupled from the route matches since they
3379- // hit a static href, so they _always_ check shouldRevalidate and the
3380- // default is strictly if a revalidation is explicitly required (action
3381- // submissions, useRevalidator, X-Remix-Revalidate).
3382- let shouldRevalidate = shouldRevalidateLoader ( fetcherMatch , {
3383- currentUrl,
3384- currentParams : state . matches [ state . matches . length - 1 ] . params ,
3385- nextUrl,
3386- nextParams : matches [ matches . length - 1 ] . params ,
3387- ...submission ,
3388- actionResult,
3389- // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
3390- defaultShouldRevalidate : isRevalidationRequired ,
3391- } ) ;
33923405 if ( shouldRevalidate ) {
33933406 revalidatingFetchers . push ( {
33943407 key,
0 commit comments