Skip to content

Commit f353a6f

Browse files
authored
Allow revalidations to complete if submitting fetcher is deleted (#10535)
1 parent e2789c1 commit f353a6f

File tree

3 files changed

+92
-13
lines changed

3 files changed

+92
-13
lines changed

.changeset/delete-submit-fetcher.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@remix-run/router": patch
3+
---
4+
5+
Allow fetcher revalidations to complete if submitting fetcher is deleted

packages/router/__tests__/router-test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10317,6 +10317,66 @@ describe("a router", () => {
1031710317
data: "TASKS ACTION",
1031810318
});
1031910319
});
10320+
10321+
it("handles revalidating fetcher when the triggering fetcher is deleted", async () => {
10322+
let key = "key";
10323+
let actionKey = "actionKey";
10324+
let t = setup({
10325+
routes: [
10326+
{
10327+
id: "root",
10328+
path: "/",
10329+
children: [
10330+
{
10331+
id: "home",
10332+
index: true,
10333+
loader: true,
10334+
},
10335+
{
10336+
id: "action",
10337+
path: "action",
10338+
action: true,
10339+
},
10340+
{
10341+
id: "fetch",
10342+
path: "fetch",
10343+
loader: true,
10344+
},
10345+
],
10346+
},
10347+
],
10348+
hydrationData: { loaderData: { home: "HOME" } },
10349+
});
10350+
10351+
// Load a fetcher
10352+
let A = await t.fetch("/fetch", key);
10353+
await A.loaders.fetch.resolve("FETCH");
10354+
10355+
// Submit a different fetcher, which will trigger revalidation
10356+
let B = await t.fetch("/action", actionKey, {
10357+
formMethod: "post",
10358+
formData: createFormData({}),
10359+
});
10360+
t.shimHelper(B.loaders, "fetch", "loader", "fetch");
10361+
10362+
// After action resolves, both fetchers go into a loading state
10363+
await B.actions.action.resolve("ACTION");
10364+
expect(t.router.state.fetchers.get(key)?.state).toBe("loading");
10365+
expect(t.router.state.fetchers.get(actionKey)?.state).toBe("loading");
10366+
10367+
// Remove the submitting fetcher (assume it's component unmounts)
10368+
t.router.deleteFetcher(actionKey);
10369+
10370+
await B.loaders.home.resolve("HOME*");
10371+
await B.loaders.fetch.resolve("FETCH*");
10372+
10373+
expect(t.router.state.loaderData).toEqual({ home: "HOME*" });
10374+
expect(t.router.state.fetchers.get(key)).toMatchObject({
10375+
state: "idle",
10376+
data: "FETCH*",
10377+
});
10378+
expect(t.router.state.fetchers.get(actionKey)).toBeUndefined();
10379+
});
1032010380
});
1032110381

1032210382
describe("fetcher ?index params", () => {

packages/router/router.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,7 +1783,6 @@ export function createRouter(init: RouterInit): Router {
17831783
let nextLocation = state.navigation.location || state.location;
17841784
let revalidationRequest = createClientSideRequest(
17851785
init.history,
1786-
17871786
nextLocation,
17881787
abortController.signal
17891788
);
@@ -1894,16 +1893,20 @@ export function createRouter(init: RouterInit): Router {
18941893
activeDeferreds
18951894
);
18961895

1897-
let doneFetcher: FetcherStates["Idle"] = {
1898-
state: "idle",
1899-
data: actionResult.data,
1900-
formMethod: undefined,
1901-
formAction: undefined,
1902-
formEncType: undefined,
1903-
formData: undefined,
1904-
" _hasFetcherDoneAnything ": true,
1905-
};
1906-
state.fetchers.set(key, doneFetcher);
1896+
// Since we let revalidations complete even if the submitting fetcher was
1897+
// deleted, only put it back to idle if it hasn't been deleted
1898+
if (state.fetchers.has(key)) {
1899+
let doneFetcher: FetcherStates["Idle"] = {
1900+
state: "idle",
1901+
data: actionResult.data,
1902+
formMethod: undefined,
1903+
formAction: undefined,
1904+
formEncType: undefined,
1905+
formData: undefined,
1906+
" _hasFetcherDoneAnything ": true,
1907+
};
1908+
state.fetchers.set(key, doneFetcher);
1909+
}
19071910

19081911
let didAbortFetchLoads = abortStaleFetchLoads(loadId);
19091912

@@ -1935,7 +1938,9 @@ export function createRouter(init: RouterInit): Router {
19351938
matches,
19361939
errors
19371940
),
1938-
...(didAbortFetchLoads ? { fetchers: new Map(state.fetchers) } : {}),
1941+
...(didAbortFetchLoads || revalidatingFetchers.length > 0
1942+
? { fetchers: new Map(state.fetchers) }
1943+
: {}),
19391944
});
19401945
isRevalidationRequired = false;
19411946
}
@@ -2271,7 +2276,16 @@ export function createRouter(init: RouterInit): Router {
22712276
}
22722277

22732278
function deleteFetcher(key: string): void {
2274-
if (fetchControllers.has(key)) abortFetcher(key);
2279+
let fetcher = state.fetchers.get(key);
2280+
// Don't abort the controller if this is a deletion of a fetcher.submit()
2281+
// in it's loading phase since - we don't want to abort the corresponding
2282+
// revalidation and want them to complete and land
2283+
if (
2284+
fetchControllers.has(key) &&
2285+
!(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))
2286+
) {
2287+
abortFetcher(key);
2288+
}
22752289
fetchLoadMatches.delete(key);
22762290
fetchReloadIds.delete(key);
22772291
fetchRedirectIds.delete(key);

0 commit comments

Comments
 (0)