Skip to content

Commit e29876c

Browse files
authored
Fix bug with search params removal (#9969)
1 parent 03da9d6 commit e29876c

File tree

7 files changed

+130
-12
lines changed

7 files changed

+130
-12
lines changed

.changeset/afraid-ducks-know.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"react-router-dom": patch
3+
"react-router-native": patch
4+
---
5+
6+
Fix bug with search params removal

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,40 @@ describe("useSearchParams", () => {
125125
);
126126
expect(node.innerHTML).toMatch(/The new query is "Ryan Florence"/);
127127
});
128+
129+
it("allows removal of search params when a default is provided", () => {
130+
function SearchPage() {
131+
let [searchParams, setSearchParams] = useSearchParams({
132+
value: "initial",
133+
});
134+
135+
return (
136+
<div>
137+
<p>The current value is "{searchParams.get("value")}".</p>
138+
<button onClick={() => setSearchParams({})}>Click</button>
139+
</div>
140+
);
141+
}
142+
143+
act(() => {
144+
ReactDOM.createRoot(node).render(
145+
<MemoryRouter initialEntries={["/search?value=initial"]}>
146+
<Routes>
147+
<Route path="search" element={<SearchPage />} />
148+
</Routes>
149+
</MemoryRouter>
150+
);
151+
});
152+
153+
let button = node.querySelector<HTMLInputElement>("button")!;
154+
expect(button).toBeDefined();
155+
156+
expect(node.innerHTML).toMatch(/The current value is "initial"/);
157+
158+
act(() => {
159+
button.dispatchEvent(new Event("click", { bubbles: true }));
160+
});
161+
162+
expect(node.innerHTML).toMatch(/The current value is ""/);
163+
});
128164
});

packages/react-router-dom/dom.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,17 @@ export function createSearchParams(
8888

8989
export function getSearchParamsForLocation(
9090
locationSearch: string,
91-
defaultSearchParams: URLSearchParams
91+
defaultSearchParams: URLSearchParams | null
9292
) {
9393
let searchParams = createSearchParams(locationSearch);
9494

95-
for (let key of defaultSearchParams.keys()) {
96-
if (!searchParams.has(key)) {
97-
defaultSearchParams.getAll(key).forEach((value) => {
98-
searchParams.append(key, value);
99-
});
95+
if (defaultSearchParams) {
96+
for (let key of defaultSearchParams.keys()) {
97+
if (!searchParams.has(key)) {
98+
defaultSearchParams.getAll(key).forEach((value) => {
99+
searchParams.append(key, value);
100+
});
101+
}
100102
}
101103
}
102104

packages/react-router-dom/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,13 +853,17 @@ export function useSearchParams(
853853
);
854854

855855
let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
856+
let hasSetSearchParamsRef = React.useRef(false);
856857

857858
let location = useLocation();
858859
let searchParams = React.useMemo(
859860
() =>
861+
// Only merge in the defaults if we haven't yet called setSearchParams.
862+
// Once we call that we want those to take precedence, otherwise you can't
863+
// remove a param with setSearchParams({}) if it has an initial value
860864
getSearchParamsForLocation(
861865
location.search,
862-
defaultSearchParamsRef.current
866+
hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current
863867
),
864868
[location.search]
865869
);
@@ -870,6 +874,7 @@ export function useSearchParams(
870874
const newSearchParams = createSearchParams(
871875
typeof nextInit === "function" ? nextInit(searchParams) : nextInit
872876
);
877+
hasSetSearchParamsRef.current = true;
873878
navigate("?" + newSearchParams, navigateOptions);
874879
},
875880
[navigate, searchParams]

packages/react-router-native/__tests__/__snapshots__/search-params-test.tsx.snap

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`useSearchParams allows removal of search params when a default is provided 1`] = `
4+
<View>
5+
<Text>
6+
The current query is "
7+
initial
8+
".
9+
</Text>
10+
<View>
11+
Click
12+
</View>
13+
</View>
14+
`;
15+
16+
exports[`useSearchParams allows removal of search params when a default is provided 2`] = `
17+
<View>
18+
<Text>
19+
The current query is "
20+
".
21+
</Text>
22+
<View>
23+
Click
24+
</View>
25+
</View>
26+
`;
27+
328
exports[`useSearchParams reads and writes the search string (functional update) 1`] = `
429
<View>
530
<Text>

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ describe("useSearchParams", () => {
1818
return <View>{children}</View>;
1919
}
2020

21+
function Button({ children }: { children: React.ReactNode; onClick?: any }) {
22+
return <View>{children}</View>;
23+
}
24+
2125
it("reads and writes the search string", () => {
2226
function SearchPage() {
2327
let [searchParams, setSearchParams] = useSearchParams({ q: "" });
@@ -112,4 +116,40 @@ describe("useSearchParams", () => {
112116

113117
expect(renderer.toJSON()).toMatchSnapshot();
114118
});
119+
120+
it("allows removal of search params when a default is provided", () => {
121+
function SearchPage() {
122+
let [searchParams, setSearchParams] = useSearchParams({
123+
value: "initial",
124+
});
125+
126+
return (
127+
<View>
128+
<Text>The current query is "{searchParams.get("value")}".</Text>
129+
<Button onClick={() => setSearchParams({})}>Click</Button>
130+
</View>
131+
);
132+
}
133+
134+
let renderer: TestRenderer.ReactTestRenderer;
135+
TestRenderer.act(() => {
136+
renderer = TestRenderer.create(
137+
<NativeRouter initialEntries={["/search?value=initial"]}>
138+
<Routes>
139+
<Route path="search" element={<SearchPage />} />
140+
</Routes>
141+
</NativeRouter>
142+
);
143+
});
144+
145+
expect(renderer.toJSON()).toMatchSnapshot();
146+
147+
let button = renderer.root.findByType(Button);
148+
149+
TestRenderer.act(() => {
150+
button.props.onClick();
151+
});
152+
153+
expect(renderer.toJSON()).toMatchSnapshot();
154+
});
115155
});

packages/react-router-native/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -288,16 +288,19 @@ export function useSearchParams(
288288
defaultInit?: URLSearchParamsInit
289289
): [URLSearchParams, SetURLSearchParams] {
290290
let defaultSearchParamsRef = React.useRef(createSearchParams(defaultInit));
291+
let hasSetSearchParamsRef = React.useRef(false);
291292

292293
let location = useLocation();
293294
let searchParams = React.useMemo(() => {
294295
let searchParams = createSearchParams(location.search);
295296

296-
for (let key of defaultSearchParamsRef.current.keys()) {
297-
if (!searchParams.has(key)) {
298-
defaultSearchParamsRef.current.getAll(key).forEach((value) => {
299-
searchParams.append(key, value);
300-
});
297+
if (!hasSetSearchParamsRef.current) {
298+
for (let key of defaultSearchParamsRef.current.keys()) {
299+
if (!searchParams.has(key)) {
300+
defaultSearchParamsRef.current.getAll(key).forEach((value) => {
301+
searchParams.append(key, value);
302+
});
303+
}
301304
}
302305
}
303306

@@ -310,6 +313,7 @@ export function useSearchParams(
310313
const newSearchParams = createSearchParams(
311314
typeof nextInit === "function" ? nextInit(searchParams) : nextInit
312315
);
316+
hasSetSearchParamsRef.current = true;
313317
navigate("?" + newSearchParams, navigateOpts);
314318
},
315319
[navigate, searchParams]

0 commit comments

Comments
 (0)