Skip to content

Commit 811e9aa

Browse files
authored
Fix bug with changing fetcher key in a mounted component (#11009)
1 parent 63960a8 commit 811e9aa

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

.changeset/changing-fetcher-key.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router-dom": patch
3+
---
4+
5+
Fix issue where a changing fetcher `key` in a `useFetcher` that remains mounted wasn't getting picked up

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5309,6 +5309,77 @@ function testDomRouter(
53095309
);
53105310
});
53115311

5312+
it("updates the key if it changes while the fetcher remains mounted", async () => {
5313+
let router = createTestRouter(
5314+
[
5315+
{
5316+
path: "/",
5317+
Component() {
5318+
let fetchers = useFetchers();
5319+
let [fetcherKey, setFetcherKey] = React.useState("a");
5320+
return (
5321+
<>
5322+
<ReusedFetcher fetcherKey={fetcherKey} />
5323+
<button onClick={() => setFetcherKey("b")}>
5324+
Change Key
5325+
</button>
5326+
<p>Fetchers:</p>
5327+
<pre>{JSON.stringify(fetchers)}</pre>
5328+
</>
5329+
);
5330+
},
5331+
},
5332+
{
5333+
path: "/echo",
5334+
loader: ({ request }) => request.url,
5335+
},
5336+
],
5337+
{ window: getWindow("/") }
5338+
);
5339+
5340+
function ReusedFetcher({ fetcherKey }: { fetcherKey: string }) {
5341+
let fetcher = useFetcher({ key: fetcherKey });
5342+
5343+
return (
5344+
<>
5345+
<button
5346+
onClick={() => fetcher.load(`/echo?fetcherKey=${fetcherKey}`)}
5347+
>
5348+
Load Fetcher
5349+
</button>
5350+
<p>{`fetcherKey:${fetcherKey}`}</p>
5351+
<p>Fetcher:{JSON.stringify(fetcher)}</p>
5352+
</>
5353+
);
5354+
}
5355+
5356+
let { container } = render(<RouterProvider router={router} />);
5357+
5358+
// Start with idle fetcher 'a'
5359+
expect(getHtml(container)).toContain('{"Form":{},"state":"idle"}');
5360+
expect(getHtml(container)).toContain("fetcherKey:a");
5361+
5362+
fireEvent.click(screen.getByText("Load Fetcher"));
5363+
await waitFor(
5364+
() => screen.getAllByText(/\/echo\?fetcherKey=a/).length > 0
5365+
);
5366+
5367+
// Fetcher 'a' now has data
5368+
expect(getHtml(container)).toContain(
5369+
'{"Form":{},"state":"idle","data":"http://localhost/echo?fetcherKey=a"}'
5370+
);
5371+
expect(getHtml(container)).toContain(
5372+
'[{"state":"idle","data":"http://localhost/echo?fetcherKey=a","key":"a"}]'
5373+
);
5374+
5375+
fireEvent.click(screen.getByText("Change Key"));
5376+
await waitFor(() => screen.getByText("fetcherKey:b"));
5377+
5378+
// We should have a new uninitialized/idle fetcher 'b'
5379+
expect(getHtml(container)).toContain('{"Form":{},"state":"idle"');
5380+
expect(getHtml(container)).toContain("[]");
5381+
});
5382+
53125383
it("exposes fetcher keys via useFetchers", async () => {
53135384
let router = createTestRouter(
53145385
[

packages/react-router-dom/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,9 @@ export function useFetcher<TData = any>({
16111611

16121612
// Fetcher key handling
16131613
let [fetcherKey, setFetcherKey] = React.useState<string>(key || "");
1614-
if (!fetcherKey) {
1614+
if (key && key !== fetcherKey) {
1615+
setFetcherKey(key);
1616+
} else if (!fetcherKey) {
16151617
setFetcherKey(getUniqueFetcherId());
16161618
}
16171619

0 commit comments

Comments
 (0)