-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly handle fetcher redirects interrupted by normal navigations #10674
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@remix-run/router": patch | ||
--- | ||
|
||
Properly handle fetcher redirects interrupted by normal navigations |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1591,7 +1591,15 @@ export function createRouter(init: RouterInit): Router { | |
// If any loaders returned a redirect Response, start a new REPLACE navigation | ||
let redirect = findRedirect(results); | ||
if (redirect) { | ||
await startRedirectNavigation(state, redirect, { replace }); | ||
if (redirect.idx >= matchesToLoad.length) { | ||
// If this redirect came from a fetcher make sure we mark it in | ||
// fetchRedirectIds so it doesn't get revalidated on the next set of | ||
// loader executions | ||
let fetcherKey = | ||
revalidatingFetchers[redirect.idx - matchesToLoad.length].key; | ||
fetchRedirectIds.add(fetcherKey); | ||
} | ||
await startRedirectNavigation(state, redirect.result, { replace }); | ||
return { shortCircuited: true }; | ||
} | ||
|
||
|
@@ -1739,6 +1747,7 @@ export function createRouter(init: RouterInit): Router { | |
); | ||
fetchControllers.set(key, abortController); | ||
|
||
let originatingLoadId = incrementingLoadId; | ||
let actionResult = await callLoaderOrAction( | ||
"action", | ||
fetchRequest, | ||
|
@@ -1760,15 +1769,26 @@ export function createRouter(init: RouterInit): Router { | |
|
||
if (isRedirectResult(actionResult)) { | ||
fetchControllers.delete(key); | ||
fetchRedirectIds.add(key); | ||
let loadingFetcher = getLoadingFetcher(submission); | ||
state.fetchers.set(key, loadingFetcher); | ||
updateState({ fetchers: new Map(state.fetchers) }); | ||
|
||
return startRedirectNavigation(state, actionResult, { | ||
submission, | ||
isFetchActionRedirect: true, | ||
}); | ||
if (pendingNavigationLoadId > originatingLoadId) { | ||
// A new navigation was kicked off after our action started, so that | ||
// should take precedence over this redirect navigation. We already | ||
// set isRevalidationRequired so all loaders for the new route should | ||
// fire unless opted out via shouldRevalidate | ||
let doneFetcher = getDoneFetcher(undefined); | ||
state.fetchers.set(key, doneFetcher); | ||
updateState({ fetchers: new Map(state.fetchers) }); | ||
return; | ||
Comment on lines
+1777
to
+1780
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flip submitting fetchers to done if they come back with a redirect after we've already started a separate navigation |
||
} else { | ||
fetchRedirectIds.add(key); | ||
let loadingFetcher = getLoadingFetcher(submission); | ||
state.fetchers.set(key, loadingFetcher); | ||
updateState({ fetchers: new Map(state.fetchers) }); | ||
|
||
return startRedirectNavigation(state, actionResult, { | ||
submission, | ||
isFetchActionRedirect: true, | ||
}); | ||
} | ||
} | ||
|
||
// Process any non-redirect errors thrown | ||
|
@@ -1875,7 +1895,15 @@ export function createRouter(init: RouterInit): Router { | |
|
||
let redirect = findRedirect(results); | ||
if (redirect) { | ||
return startRedirectNavigation(state, redirect); | ||
if (redirect.idx >= matchesToLoad.length) { | ||
// If this redirect came from a fetcher make sure we mark it in | ||
// fetchRedirectIds so it doesn't get revalidated on the next set of | ||
// loader executions | ||
let fetcherKey = | ||
revalidatingFetchers[redirect.idx - matchesToLoad.length].key; | ||
fetchRedirectIds.add(fetcherKey); | ||
} | ||
return startRedirectNavigation(state, redirect.result); | ||
} | ||
|
||
// Process and commit output from loaders | ||
|
@@ -1962,6 +1990,7 @@ export function createRouter(init: RouterInit): Router { | |
); | ||
fetchControllers.set(key, abortController); | ||
|
||
let originatingLoadId = incrementingLoadId; | ||
let result: DataResult = await callLoaderOrAction( | ||
"loader", | ||
fetchRequest, | ||
|
@@ -1994,9 +2023,18 @@ export function createRouter(init: RouterInit): Router { | |
|
||
// If the loader threw a redirect Response, start a new REPLACE navigation | ||
if (isRedirectResult(result)) { | ||
fetchRedirectIds.add(key); | ||
await startRedirectNavigation(state, result); | ||
return; | ||
if (pendingNavigationLoadId > originatingLoadId) { | ||
// A new navigation was kicked off after our loader started, so that | ||
// should take precedence over this redirect navigation | ||
let doneFetcher = getDoneFetcher(undefined); | ||
state.fetchers.set(key, doneFetcher); | ||
updateState({ fetchers: new Map(state.fetchers) }); | ||
return; | ||
Comment on lines
+2029
to
+2032
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flip loading fetchers to done if they come back with a redirect after we've already started a separate navigation. Note that they'll be eligible for revalidation so they'll run again and if they redirect that time it will be processed |
||
} else { | ||
fetchRedirectIds.add(key); | ||
await startRedirectNavigation(state, result); | ||
return; | ||
} | ||
} | ||
|
||
// Process any non-redirect errors thrown | ||
|
@@ -4090,11 +4128,13 @@ function getInternalRouterError( | |
} | ||
|
||
// Find any returned redirect errors, starting from the lowest match | ||
function findRedirect(results: DataResult[]): RedirectResult | undefined { | ||
function findRedirect( | ||
results: DataResult[] | ||
): { result: RedirectResult; idx: number } | undefined { | ||
for (let i = results.length - 1; i >= 0; i--) { | ||
let result = results[i]; | ||
if (isRedirectResult(result)) { | ||
return result; | ||
return { result, idx: i }; | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding to
fetchRedirectIds
avoids redirect loops of revalidating fetchers