Skip to content

Commit 26ecf2f

Browse files
authored
Fix fetcher state stuck on loading if a loader redirects (#12873)
1 parent 5a966cb commit 26ecf2f

File tree

4 files changed

+50
-7
lines changed

4 files changed

+50
-7
lines changed

.changeset/curvy-lobsters-accept.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Fix bug where a submitting `fetcher` would get stuck in a `loading` state if a revalidating `loader` redirected

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
- fyzhu
112112
- fz6m
113113
- gaspard
114+
- gatzjames
114115
- gavriguy
115116
- Geist5000
116117
- gesposito

packages/react-router/__tests__/router/fetchers-test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,43 @@ describe("fetchers", () => {
841841
expect(t.router.state.location.pathname).toBe("/");
842842
expect(t.router.state.location.key).toBe(key);
843843
});
844+
845+
test("handles loader redirects after a fetcher submission", async () => {
846+
let t = initializeTest();
847+
848+
let A = await t.navigate("/foo");
849+
await A.loaders.foo.resolve("FOO");
850+
expect(t.router.state).toMatchObject({
851+
location: { pathname: "/foo" },
852+
navigation: { state: "idle" },
853+
loaderData: { root: "ROOT", foo: "FOO" },
854+
});
855+
856+
let key = "key";
857+
let B = await t.fetch("/bar", key, {
858+
formMethod: "post",
859+
formData: createFormData({}),
860+
});
861+
await B.actions.bar.resolve("ACTION");
862+
expect(t.fetchers[key]).toMatchObject({
863+
state: "loading",
864+
data: "ACTION",
865+
});
866+
await B.loaders.root.resolve("ROOT*");
867+
868+
let C = await B.loaders.foo.redirect("/");
869+
await C.loaders.root.resolve("ROOT**");
870+
await C.loaders.index.resolve("INDEX*");
871+
expect(t.router.state).toMatchObject({
872+
location: { pathname: "/" },
873+
navigation: { state: "idle" },
874+
loaderData: { root: "ROOT**", index: "INDEX*" },
875+
});
876+
expect(t.fetchers[key]).toMatchObject({
877+
state: "idle",
878+
data: "ACTION",
879+
});
880+
});
844881
});
845882

846883
describe("fetcher resubmissions/re-gets", () => {

packages/react-router/lib/router/router.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2494,6 +2494,13 @@ export function createRouter(init: RouterInit): Router {
24942494
fetchControllers.delete(key);
24952495
revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
24962496

2497+
// Since we let revalidations complete even if the submitting fetcher was
2498+
// deleted, only put it back to idle if it hasn't been deleted
2499+
if (state.fetchers.has(key)) {
2500+
let doneFetcher = getDoneFetcher(actionResult.data);
2501+
state.fetchers.set(key, doneFetcher);
2502+
}
2503+
24972504
let redirect = findRedirect(loaderResults);
24982505
if (redirect) {
24992506
return startRedirectNavigation(
@@ -2528,13 +2535,6 @@ export function createRouter(init: RouterInit): Router {
25282535
fetcherResults
25292536
);
25302537

2531-
// Since we let revalidations complete even if the submitting fetcher was
2532-
// deleted, only put it back to idle if it hasn't been deleted
2533-
if (state.fetchers.has(key)) {
2534-
let doneFetcher = getDoneFetcher(actionResult.data);
2535-
state.fetchers.set(key, doneFetcher);
2536-
}
2537-
25382538
abortStaleFetchLoads(loadId);
25392539

25402540
// If we are currently in a navigation loading state and this fetcher is

0 commit comments

Comments
 (0)