Skip to content

Commit 5f530a7

Browse files
authored
Do not revalidate unmounted fetchers when v7_persistFetcher is enabled (#11044)
1 parent f320378 commit 5f530a7

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed
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+
Do not revalidate unmounted fetchers when `v7_fetcherPersist` is enabled

packages/react-router-dom/__tests__/data-browser-router-test.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6288,6 +6288,107 @@ function testDomRouter(
62886288
await waitFor(() => screen.getByText("FETCH ERROR"));
62896289
expect(getHtml(container)).toMatch("Unexpected Application Error!");
62906290
});
6291+
6292+
it("unmounted fetchers should not revalidate", async () => {
6293+
let count = 0;
6294+
let loaderDfd = createDeferred();
6295+
let actionDfd = createDeferred();
6296+
let router = createTestRouter(
6297+
[
6298+
{
6299+
path: "/",
6300+
action: () => actionDfd.promise,
6301+
Component() {
6302+
let [showFetcher, setShowFetcher] = React.useState(true);
6303+
let [fetcherData, setFetcherData] = React.useState(null);
6304+
let fetchers = useFetchers();
6305+
let actionData = useActionData();
6306+
let navigation = useNavigation();
6307+
6308+
return (
6309+
<>
6310+
<Form method="post">
6311+
<button type="submit">Submit Form</button>
6312+
<p>{`Navigation State: ${navigation.state}`}</p>
6313+
<p>{`Action Data: ${actionData}`}</p>
6314+
<p>{`Active Fetchers: ${fetchers.length}`}</p>
6315+
</Form>
6316+
{showFetcher ? (
6317+
<FetcherComponent
6318+
onClose={(data) => {
6319+
setFetcherData(data);
6320+
setShowFetcher(false);
6321+
}}
6322+
/>
6323+
) : (
6324+
<p>{fetcherData}</p>
6325+
)}
6326+
</>
6327+
);
6328+
},
6329+
},
6330+
{
6331+
path: "/fetch",
6332+
async loader() {
6333+
count++;
6334+
if (count === 1) return await loaderDfd.promise;
6335+
throw new Error("Fetcher load called too many times");
6336+
},
6337+
},
6338+
],
6339+
{ window: getWindow("/"), future: { v7_fetcherPersist: true } }
6340+
);
6341+
6342+
function FetcherComponent({ onClose }) {
6343+
let fetcher = useFetcher();
6344+
6345+
React.useEffect(() => {
6346+
if (fetcher.state === "idle" && fetcher.data) {
6347+
onClose(fetcher.data);
6348+
}
6349+
}, [fetcher, onClose]);
6350+
6351+
return (
6352+
<>
6353+
<button onClick={() => fetcher.load("/fetch")}>
6354+
Load Fetcher
6355+
</button>
6356+
<pre>{`Fetcher State: ${fetcher.state}`}</pre>
6357+
</>
6358+
);
6359+
}
6360+
6361+
render(<RouterProvider router={router} />);
6362+
6363+
fireEvent.click(screen.getByText("Load Fetcher"));
6364+
await waitFor(
6365+
() =>
6366+
screen.getByText("Active Fetchers: 1") &&
6367+
screen.getByText("Fetcher State: loading")
6368+
);
6369+
6370+
loaderDfd.resolve("FETCHER DATA");
6371+
await waitFor(
6372+
() =>
6373+
screen.getByText("FETCHER DATA") &&
6374+
screen.getByText("Active Fetchers: 0")
6375+
);
6376+
6377+
fireEvent.click(screen.getByText("Submit Form"));
6378+
await waitFor(() =>
6379+
screen.getByText("Navigation State: submitting")
6380+
);
6381+
6382+
actionDfd.resolve("ACTION");
6383+
await waitFor(
6384+
() =>
6385+
screen.getByText("Navigation State: idle") &&
6386+
screen.getByText("Active Fetchers: 0") &&
6387+
screen.getByText("Action Data: ACTION")
6388+
);
6389+
6390+
expect(count).toBe(1);
6391+
});
62916392
});
62926393
});
62936394

packages/router/router.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,7 @@ export function createRouter(init: RouterInit): Router {
16321632
isRevalidationRequired,
16331633
cancelledDeferredRoutes,
16341634
cancelledFetcherLoads,
1635+
deletedFetchers,
16351636
fetchLoadMatches,
16361637
fetchRedirectIds,
16371638
routesToUse,
@@ -2005,6 +2006,7 @@ export function createRouter(init: RouterInit): Router {
20052006
isRevalidationRequired,
20062007
cancelledDeferredRoutes,
20072008
cancelledFetcherLoads,
2009+
deletedFetchers,
20082010
fetchLoadMatches,
20092011
fetchRedirectIds,
20102012
routesToUse,
@@ -3549,6 +3551,7 @@ function getMatchesToLoad(
35493551
isRevalidationRequired: boolean,
35503552
cancelledDeferredRoutes: string[],
35513553
cancelledFetcherLoads: string[],
3554+
deletedFetchers: Set<string>,
35523555
fetchLoadMatches: Map<string, FetchLoadMatch>,
35533556
fetchRedirectIds: Set<string>,
35543557
routesToUse: AgnosticDataRouteObject[],
@@ -3616,7 +3619,10 @@ function getMatchesToLoad(
36163619
let revalidatingFetchers: RevalidatingFetcher[] = [];
36173620
fetchLoadMatches.forEach((f, key) => {
36183621
// Don't revalidate if fetcher won't be present in the subsequent render
3619-
if (!matches.some((m) => m.route.id === f.routeId)) {
3622+
if (
3623+
!matches.some((m) => m.route.id === f.routeId) ||
3624+
deletedFetchers.has(key)
3625+
) {
36203626
return;
36213627
}
36223628

0 commit comments

Comments
 (0)