Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@
- turansky
- underager
- vijaypushkin
- promet99
13 changes: 10 additions & 3 deletions docs/hooks/use-search-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@ type URLSearchParamsInit =
| URLSearchParams;

type SetURLSearchParams = (
nextInit?: URLSearchParamsInit,
navigateOpts?: : { replace?: boolean; state?: any }
nextInit?:
| URLSearchParamsInit
| ((prev: URLSearchParams) => URLSearchParamsInit),
navigateOpts?: : NavigateOptions
) => void;

interface NavigateOptions {
replace?: boolean;
state?: any;
}
```

</details>

The `useSearchParams` hook is used to read and modify the query string in the URL for the current location. Like React's own [`useState` hook](https://reactjs.org/docs/hooks-reference.html#usestate), `useSearchParams` returns an array of two values: the current location's [search params](https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams) and a function that may be used to update them.
The `useSearchParams` hook is used to read and modify the query string in the URL for the current location. Like React's own [`useState` hook](https://reactjs.org/docs/hooks-reference.html#usestate), `useSearchParams` returns an array of two values: the current location's [search params](https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams) and a function that may be used to update them. Just as React's [`useState` hook](https://reactjs.org/docs/hooks-reference.html#usestate), setSearchParams also supports [functional updates](https://reactjs.org/docs/hooks-reference.html#functional-updates). Therefore, if you provide a function (that takes a searchParams and modify and then return it) to setSearchParams, that function will be applied.

```tsx
import * as React from "react";
Expand Down
64 changes: 63 additions & 1 deletion packages/react-router-dom/__tests__/search-params-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ describe("useSearchParams", () => {
);
}

function SearchPageFunctionalUpdate() {
let queryRef = React.useRef<HTMLInputElement>(null);
let [searchParams, setSearchParams] = useSearchParams({
d: "Ryan",
});
let queryD = searchParams.get("d")!;
let queryQ = searchParams.get("q")!;

function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (queryRef.current) {
setSearchParams((cur) => {
let d = cur.get("d");
cur.set("d", d + " Florence");
cur.delete("q");
return cur;
});
}
}

return (
<div>
<p>
d: {queryD}, q: {queryQ}
</p>
<form onSubmit={handleSubmit}>
<input name="q" defaultValue={queryQ} ref={queryRef} />
</form>
</div>
);
}

let node: HTMLDivElement;
beforeEach(() => {
node = document.createElement("div");
Expand Down Expand Up @@ -58,12 +90,42 @@ describe("useSearchParams", () => {
expect(node.innerHTML).toMatch(/The current query is "Michael Jackson"/);

act(() => {
queryInput.value = "Ryan Florence";
queryInput.value = " Florence";
form.dispatchEvent(
new Event("submit", { bubbles: true, cancelable: true })
);
});

expect(node.innerHTML).toMatch(/The current query is "Ryan Florence"/);
});

it("updates searchParams when a function is provided to setSearchParams (functional updates)", () => {
act(() => {
ReactDOM.render(
<MemoryRouter initialEntries={["/search?q=UrlValue"]}>
<Routes>
<Route path="search" element={<SearchPageFunctionalUpdate />} />
</Routes>
</MemoryRouter>,
node
);
});

let form = node.querySelector("form")!;
expect(form).toBeDefined();

let queryInput = node.querySelector<HTMLInputElement>("input[name=q]")!;
expect(queryInput).toBeDefined();

expect(node.innerHTML).toMatch(/d: Ryan, q: UrlValue/);

act(() => {
queryInput.value = "Ryan Florence";
form.dispatchEvent(
new Event("submit", { bubbles: true, cancelable: true })
);
});

expect(node.innerHTML).toMatch(/d: Ryan Florence, q: /);
});
});
27 changes: 18 additions & 9 deletions packages/react-router-dom/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,9 @@ export function useLinkClickHandler<E extends Element = HTMLAnchorElement>(
* A convenient wrapper for reading and writing search parameters via the
* URLSearchParams interface.
*/
export function useSearchParams(defaultInit?: URLSearchParamsInit) {
export function useSearchParams(
defaultInit?: URLSearchParamsInit
): [URLSearchParams, SetURLSearchParams] {
warning(
typeof URLSearchParams !== "undefined",
`You cannot use the \`useSearchParams\` hook in a browser that does not ` +
Expand Down Expand Up @@ -457,19 +459,26 @@ export function useSearchParams(defaultInit?: URLSearchParamsInit) {
}, [location.search]);

let navigate = useNavigate();
let setSearchParams = React.useCallback(
(
nextInit: URLSearchParamsInit,
navigateOptions?: { replace?: boolean; state?: any }
) => {
navigate("?" + createSearchParams(nextInit), navigateOptions);
let setSearchParams = React.useCallback<SetURLSearchParams>(
(nextInit, navigateOptions) => {
const newSearchParams = createSearchParams(
typeof nextInit === "function" ? nextInit(searchParams) : nextInit
);
navigate("?" + newSearchParams, navigateOptions);
},
[navigate]
[navigate, searchParams]
);

return [searchParams, setSearchParams] as const;
return [searchParams, setSearchParams];
}

type SetURLSearchParams = (
nextInit?:
| URLSearchParamsInit
| ((prev: URLSearchParams) => URLSearchParamsInit),
navigateOpts?: { replace?: boolean; state?: any }
) => void;

export type ParamKeyValuePair = [string, string];

export type URLSearchParamsInit =
Expand Down