From 2acc01bdf9e8f8b5da7b1e5f48365616a2fa8b3d Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Thu, 9 Nov 2023 10:34:59 -0500 Subject: [PATCH 1/2] Fix bug with changing fetcher key in a mounted component --- .changeset/changing-fetcher-key.md | 5 ++ .../__tests__/data-browser-router-test.tsx | 71 +++++++++++++++++++ packages/react-router-dom/index.tsx | 6 ++ 3 files changed, 82 insertions(+) create mode 100644 .changeset/changing-fetcher-key.md diff --git a/.changeset/changing-fetcher-key.md b/.changeset/changing-fetcher-key.md new file mode 100644 index 0000000000..fc97589d2b --- /dev/null +++ b/.changeset/changing-fetcher-key.md @@ -0,0 +1,5 @@ +--- +"react-router-dom": patch +--- + +Fix issue where a changing fetcher `key` in a `useFetcher` that remains mounted wasn't getting picked up 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 45d8a5f92b..574b653816 100644 --- a/packages/react-router-dom/__tests__/data-browser-router-test.tsx +++ b/packages/react-router-dom/__tests__/data-browser-router-test.tsx @@ -5309,6 +5309,77 @@ function testDomRouter( ); }); + it("updates the key if it changes while the fetcher remains mounted", async () => { + let router = createTestRouter( + [ + { + path: "/", + Component() { + let fetchers = useFetchers(); + let [fetcherKey, setFetcherKey] = React.useState("a"); + return ( + <> + + +

Fetchers:

+
{JSON.stringify(fetchers)}
+ + ); + }, + }, + { + path: "/echo", + loader: ({ request }) => request.url, + }, + ], + { window: getWindow("/") } + ); + + function ReusedFetcher({ fetcherKey }: { fetcherKey: string }) { + let fetcher = useFetcher({ key: fetcherKey }); + + return ( + <> + +

{`fetcherKey:${fetcherKey}`}

+

Fetcher:{JSON.stringify(fetcher)}

+ + ); + } + + let { container } = render(); + + // Start with idle fetcher 'a' + expect(getHtml(container)).toContain('{"Form":{},"state":"idle"}'); + expect(getHtml(container)).toContain("fetcherKey:a"); + + fireEvent.click(screen.getByText("Load Fetcher")); + await waitFor( + () => screen.getAllByText(/\/echo\?fetcherKey=a/).length > 0 + ); + + // Fetcher 'a' now has data + expect(getHtml(container)).toContain( + '{"Form":{},"state":"idle","data":"http://localhost/echo?fetcherKey=a"}' + ); + expect(getHtml(container)).toContain( + '[{"state":"idle","data":"http://localhost/echo?fetcherKey=a","key":"a"}]' + ); + + fireEvent.click(screen.getByText("Change Key")); + await waitFor(() => screen.getByText("fetcherKey:b")); + + // We should have a new uninitialized/idle fetcher 'b' + expect(getHtml(container)).toContain('{"Form":{},"state":"idle"'); + expect(getHtml(container)).toContain("[]"); + }); + it("exposes fetcher keys via useFetchers", async () => { let router = createTestRouter( [ diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index ec1bc769d2..f90a274685 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -1551,6 +1551,12 @@ export function useFetcher({ setFetcherKey(getUniqueFetcherId()); } + React.useEffect(() => { + if (key && key !== "" && key != fetcherKey) { + setFetcherKey(key); + } + }, [key, fetcherKey]); + // Registration/cleanup React.useEffect(() => { router.getFetcher(fetcherKey); From 3639f21a1f9bc9c488e9ec7e96de8edf63b59c77 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 13 Nov 2023 16:58:13 -0500 Subject: [PATCH 2/2] set state in render instead of an effect --- packages/react-router-dom/index.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index f90a274685..6eaba454b4 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -1547,16 +1547,12 @@ export function useFetcher({ // Fetcher key handling let [fetcherKey, setFetcherKey] = React.useState(key || ""); - if (!fetcherKey) { + if (key && key !== fetcherKey) { + setFetcherKey(key); + } else if (!fetcherKey) { setFetcherKey(getUniqueFetcherId()); } - React.useEffect(() => { - if (key && key !== "" && key != fetcherKey) { - setFetcherKey(key); - } - }, [key, fetcherKey]); - // Registration/cleanup React.useEffect(() => { router.getFetcher(fetcherKey);