From 718b926af8430729ff3b96a13b77abd9ff2951eb Mon Sep 17 00:00:00 2001 From: Chris Park <54811538+promet99@users.noreply.github.com> Date: Sat, 28 May 2022 18:39:07 +0900 Subject: [PATCH 1/2] [Feat] add functional update to useSearchParams --- docs/hooks/use-search-params.md | 13 +++- .../__tests__/search-params-test.tsx | 64 ++++++++++++++++++- packages/react-router-dom/index.tsx | 27 +++++--- 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/docs/hooks/use-search-params.md b/docs/hooks/use-search-params.md index 11fcda0810..8ff87b213e 100644 --- a/docs/hooks/use-search-params.md +++ b/docs/hooks/use-search-params.md @@ -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; +} ``` -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"; diff --git a/packages/react-router-dom/__tests__/search-params-test.tsx b/packages/react-router-dom/__tests__/search-params-test.tsx index 41f1510754..06053a1468 100644 --- a/packages/react-router-dom/__tests__/search-params-test.tsx +++ b/packages/react-router-dom/__tests__/search-params-test.tsx @@ -26,6 +26,38 @@ describe("useSearchParams", () => { ); } + function SearchPageFunctionalUpdate() { + let queryRef = React.useRef(null); + let [searchParams, setSearchParams] = useSearchParams({ + d: "Ryan", + }); + let queryD = searchParams.get("d")!; + let queryQ = searchParams.get("q")!; + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + if (queryRef.current) { + setSearchParams((cur) => { + let d = cur.get("d"); + cur.set("d", d + " Florence"); + cur.delete("q"); + return cur; + }); + } + } + + return ( +
+

+ d: {queryD}, q: {queryQ} +

+
+ +
+
+ ); + } + let node: HTMLDivElement; beforeEach(() => { node = document.createElement("div"); @@ -58,7 +90,7 @@ 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 }) ); @@ -66,4 +98,34 @@ describe("useSearchParams", () => { 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( + + + } /> + + , + node + ); + }); + + let form = node.querySelector("form")!; + expect(form).toBeDefined(); + + let queryInput = node.querySelector("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: /); + }); }); diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 83ecd0e83b..451f3b813f 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -426,7 +426,9 @@ export function useLinkClickHandler( * 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 ` + @@ -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( + (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 = From 906def5160a61597b7ad449f1e9f78e4ca7cd175 Mon Sep 17 00:00:00 2001 From: Chris Park <54811538+promet99@users.noreply.github.com> Date: Sat, 28 May 2022 18:39:18 +0900 Subject: [PATCH 2/2] add contributor --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index ce76119c45..a15fede75a 100644 --- a/contributors.yml +++ b/contributors.yml @@ -61,3 +61,4 @@ - turansky - underager - vijaypushkin +- promet99