From 5f530a775cd266940f725894277b6ea7bc55b5d0 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 20 Nov 2023 15:04:43 -0500 Subject: [PATCH] Do not revalidate unmounted fetchers when v7_persistFetcher is enabled (#11044) --- .changeset/unmounted-fetcher-revalidate.md | 5 + .../__tests__/data-browser-router-test.tsx | 101 ++++++++++++++++++ packages/router/router.ts | 8 +- 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .changeset/unmounted-fetcher-revalidate.md diff --git a/.changeset/unmounted-fetcher-revalidate.md b/.changeset/unmounted-fetcher-revalidate.md new file mode 100644 index 0000000000..feb70e08f8 --- /dev/null +++ b/.changeset/unmounted-fetcher-revalidate.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Do not revalidate unmounted fetchers when `v7_fetcherPersist` is enabled diff --git a/packages/react-router-dom/__tests__/data-browser-router-test.tsx b/packages/react-router-dom/__tests__/data-browser-router-test.tsx index 1c8d57221e..af2f85c214 100644 --- a/packages/react-router-dom/__tests__/data-browser-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-browser-router-test.tsx @@ -6288,6 +6288,107 @@ function testDomRouter( await waitFor(() => screen.getByText("FETCH ERROR")); expect(getHtml(container)).toMatch("Unexpected Application Error!"); }); + + it("unmounted fetchers should not revalidate", async () => { + let count = 0; + let loaderDfd = createDeferred(); + let actionDfd = createDeferred(); + let router = createTestRouter( + [ + { + path: "/", + action: () => actionDfd.promise, + Component() { + let [showFetcher, setShowFetcher] = React.useState(true); + let [fetcherData, setFetcherData] = React.useState(null); + let fetchers = useFetchers(); + let actionData = useActionData(); + let navigation = useNavigation(); + + return ( + <> +
+ +

{`Navigation State: ${navigation.state}`}

+

{`Action Data: ${actionData}`}

+

{`Active Fetchers: ${fetchers.length}`}

+
+ {showFetcher ? ( + { + setFetcherData(data); + setShowFetcher(false); + }} + /> + ) : ( +

{fetcherData}

+ )} + + ); + }, + }, + { + path: "/fetch", + async loader() { + count++; + if (count === 1) return await loaderDfd.promise; + throw new Error("Fetcher load called too many times"); + }, + }, + ], + { window: getWindow("/"), future: { v7_fetcherPersist: true } } + ); + + function FetcherComponent({ onClose }) { + let fetcher = useFetcher(); + + React.useEffect(() => { + if (fetcher.state === "idle" && fetcher.data) { + onClose(fetcher.data); + } + }, [fetcher, onClose]); + + return ( + <> + +
{`Fetcher State: ${fetcher.state}`}
+ + ); + } + + render(); + + fireEvent.click(screen.getByText("Load Fetcher")); + await waitFor( + () => + screen.getByText("Active Fetchers: 1") && + screen.getByText("Fetcher State: loading") + ); + + loaderDfd.resolve("FETCHER DATA"); + await waitFor( + () => + screen.getByText("FETCHER DATA") && + screen.getByText("Active Fetchers: 0") + ); + + fireEvent.click(screen.getByText("Submit Form")); + await waitFor(() => + screen.getByText("Navigation State: submitting") + ); + + actionDfd.resolve("ACTION"); + await waitFor( + () => + screen.getByText("Navigation State: idle") && + screen.getByText("Active Fetchers: 0") && + screen.getByText("Action Data: ACTION") + ); + + expect(count).toBe(1); + }); }); }); diff --git a/packages/router/router.ts b/packages/router/router.ts index c14c4814da..21453256af 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -1632,6 +1632,7 @@ export function createRouter(init: RouterInit): Router { isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, + deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, @@ -2005,6 +2006,7 @@ export function createRouter(init: RouterInit): Router { isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, + deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, @@ -3549,6 +3551,7 @@ function getMatchesToLoad( isRevalidationRequired: boolean, cancelledDeferredRoutes: string[], cancelledFetcherLoads: string[], + deletedFetchers: Set, fetchLoadMatches: Map, fetchRedirectIds: Set, routesToUse: AgnosticDataRouteObject[], @@ -3616,7 +3619,10 @@ function getMatchesToLoad( let revalidatingFetchers: RevalidatingFetcher[] = []; fetchLoadMatches.forEach((f, key) => { // Don't revalidate if fetcher won't be present in the subsequent render - if (!matches.some((m) => m.route.id === f.routeId)) { + if ( + !matches.some((m) => m.route.id === f.routeId) || + deletedFetchers.has(key) + ) { return; }