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..6eaba454b4 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -1547,7 +1547,9 @@ 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()); }