Skip to content

Commit a250960

Browse files
yuhwan-parktimdorrbrophdawg11
authored
fix(react-router): fix useSearchParams when used together w/ useBlocker (#12784)
Co-authored-by: Tim Dorr <[email protected]> Co-authored-by: Matt Brophy <[email protected]>
1 parent 633bbe7 commit a250960

File tree

4 files changed

+122
-2
lines changed

4 files changed

+122
-2
lines changed

.changeset/curly-sloths-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-router": patch
3+
---
4+
5+
Pass a copy of `searchParams` to the `setSearchParams` callback function to avoid muations of the internal `searchParams` instance. This was an issue when navigations were blocked because the internal instance be out of sync with `useLocation().search`.

contributors.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@
403403
- yionr
404404
- yracnet
405405
- ytori
406+
- yuhwan-park
406407
- yuleicul
407408
- zeevick10
408409
- zeromask1337

packages/react-router/__tests__/dom/search-params-test.tsx

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import * as React from "react";
22
import * as ReactDOM from "react-dom/client";
33
import { act } from "@testing-library/react";
4-
import { MemoryRouter, Routes, Route, useSearchParams } from "../../index";
4+
import {
5+
MemoryRouter,
6+
Routes,
7+
Route,
8+
useSearchParams,
9+
createBrowserRouter,
10+
useBlocker,
11+
RouterProvider,
12+
useLocation,
13+
} from "../../index";
514

615
describe("useSearchParams", () => {
716
let node: HTMLDivElement;
@@ -182,4 +191,107 @@ describe("useSearchParams", () => {
182191
`"<p>value=initial&amp;a=1&amp;b=2</p>"`
183192
);
184193
});
194+
195+
it("does not reflect functional update mutation when navigation is blocked", () => {
196+
let router = createBrowserRouter([
197+
{
198+
path: "/",
199+
Component() {
200+
let location = useLocation();
201+
let [searchParams, setSearchParams] = useSearchParams();
202+
let [shouldBlock, setShouldBlock] = React.useState(false);
203+
let b = useBlocker(shouldBlock);
204+
return (
205+
<>
206+
<pre id="output">
207+
{`location.search=${location.search}`}
208+
{`searchParams=${searchParams.toString()}`}
209+
{`blocked=${b.state}`}
210+
</pre>
211+
<button
212+
id="toggle-blocking"
213+
onClick={() => setShouldBlock(!shouldBlock)}
214+
>
215+
Toggle Blocking
216+
</button>
217+
<button
218+
id="navigate1"
219+
onClick={() => {
220+
setSearchParams((prev) => {
221+
prev.set("foo", "bar");
222+
return prev;
223+
});
224+
}}
225+
>
226+
Navigate 1
227+
</button>
228+
<button
229+
id="navigate2"
230+
onClick={() => {
231+
setSearchParams((prev) => {
232+
prev.set("foo", "baz");
233+
return prev;
234+
});
235+
}}
236+
>
237+
Navigate 2
238+
</button>
239+
</>
240+
);
241+
},
242+
},
243+
]);
244+
245+
act(() => {
246+
ReactDOM.createRoot(node).render(<RouterProvider router={router} />);
247+
});
248+
249+
expect(node.querySelector("#output")).toMatchInlineSnapshot(`
250+
<pre
251+
id="output"
252+
>
253+
location.search=
254+
searchParams=
255+
blocked=unblocked
256+
</pre>
257+
`);
258+
259+
act(() => {
260+
node
261+
.querySelector("#navigate1")!
262+
.dispatchEvent(new Event("click", { bubbles: true }));
263+
});
264+
265+
expect(node.querySelector("#output")).toMatchInlineSnapshot(`
266+
<pre
267+
id="output"
268+
>
269+
location.search=?foo=bar
270+
searchParams=foo=bar
271+
blocked=unblocked
272+
</pre>
273+
`);
274+
275+
act(() => {
276+
node
277+
.querySelector("#toggle-blocking")!
278+
.dispatchEvent(new Event("click", { bubbles: true }));
279+
});
280+
281+
act(() => {
282+
node
283+
.querySelector("#navigate2")!
284+
.dispatchEvent(new Event("click", { bubbles: true }));
285+
});
286+
287+
expect(node.querySelector("#output")).toMatchInlineSnapshot(`
288+
<pre
289+
id="output"
290+
>
291+
location.search=?foo=bar
292+
searchParams=foo=bar
293+
blocked=blocked
294+
</pre>
295+
`);
296+
});
185297
});

packages/react-router/lib/dom/lib.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,9 @@ export function useSearchParams(
14241424
let setSearchParams = React.useCallback<SetURLSearchParams>(
14251425
(nextInit, navigateOptions) => {
14261426
const newSearchParams = createSearchParams(
1427-
typeof nextInit === "function" ? nextInit(searchParams) : nextInit
1427+
typeof nextInit === "function"
1428+
? nextInit(new URLSearchParams(searchParams))
1429+
: nextInit
14281430
);
14291431
hasSetSearchParamsRef.current = true;
14301432
navigate("?" + newSearchParams, navigateOptions);

0 commit comments

Comments
 (0)