Repository navigation
useSearchParams for extracting and modifying search parameters #368
Description
Activity
I was also facing the issue of extracting search params. But I have built a custom hook for that. I can raise a pull request for it.
Hi! Sorry, this isn't well documented (yet! PRs are welcome). You can subscribe to search string updates via:
import { useSearch, useLocationProperty, navigate } from 'wouter/use-location'; // get all search params: const search = useSearch();
Note that you will have to parse these using
URLSearchParamsor a 3rd party library. We might add this in the future releases.@molefrog Actually the issue isn't getting the search params, it's actually updating them. We need a hook like useSearchParams of React Router Dom, to retrieve as well as update the search params.
I see, this isn't provided out-of-the-box right now. But I'll try to hack a simple implementation for that.
I've written the
useSearchParamsfunction based on react-router's implementation forwouter. The API is similar if not identical to react-router's useSearchParams version.So far it has been working great for me, hope it helps :)
lib/wouter.tsimport { useCallback, useMemo, useRef } from 'react'; import { navigate, useSearch } from 'wouter/use-location'; // Based on react-router: https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/index.tsx type ParamKeyValuePair = [string, string]; type URLSearchParamsInit = | string | ParamKeyValuePair[] | Record<string, string | string[]> | URLSearchParams; export function createSearchParams( init: URLSearchParamsInit = '', ): URLSearchParams { return new URLSearchParams( typeof init === 'string' || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).reduce((memo, key) => { const value = init[key]; return memo.concat( Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]], ); }, [] as ParamKeyValuePair[]), ); } export function getSearchParamsForLocation( locationSearch: string, defaultSearchParams: URLSearchParams | null, ) { const searchParams = createSearchParams(locationSearch); if (defaultSearchParams) { // Use `defaultSearchParams.forEach(...)` here instead of iterating of // `defaultSearchParams.keys()` to work-around a bug in Firefox related to // web extensions. Relevant Bugzilla tickets: // https://bugzilla.mozilla.org/show_bug.cgi?id=1414602 // https://bugzilla.mozilla.org/show_bug.cgi?id=1023984 defaultSearchParams.forEach((_, key) => { if (!searchParams.has(key)) { defaultSearchParams.getAll(key).forEach((value) => { searchParams.append(key, value); }); } }); } return searchParams; } export function useSearchParams(defaultInit?: URLSearchParamsInit) { if (typeof URLSearchParams === 'undefined') { console.warn( `You cannot use the \`useSearchParams\` hook in a browser that does not ` + `support the URLSearchParams API. If you need to support Internet ` + `Explorer 11, we recommend you load a polyfill such as ` + `https://github.com/ungap/url-search-params\n\n` + `If you're unsure how to load polyfills, we recommend you check out ` + `https://polyfill.io/v3/ which provides some recommendations about how ` + `to load polyfills only for users that need them, instead of for every ` + `user.`, ); } const defaultSearchParamsRef = useRef(createSearchParams(defaultInit)); const hasSetSearchParamsRef = useRef(false); const search = useSearch(); const searchParams = useMemo( () => // Only merge in the defaults if we haven't yet called setSearchParams. // Once we call that we want those to take precedence, otherwise you can't // remove a param with setSearchParams({}) if it has an initial value getSearchParamsForLocation( search, hasSetSearchParamsRef.current ? null : defaultSearchParamsRef.current, ), [search], ); const setSearchParams = useCallback( ( nextInit: | URLSearchParamsInit | ((prev: URLSearchParams) => URLSearchParamsInit), navigateOpts?: Parameters<typeof navigate>['1'], ) => { const newSearchParams = createSearchParams( typeof nextInit === 'function' ? nextInit(searchParams) : nextInit, ); hasSetSearchParamsRef.current = true; navigate('?' + newSearchParams, navigateOpts); }, [searchParams], ); return [searchParams, setSearchParams] as const; }
app.tsxexport function App() { const [searchParams, setSearchParams] = useSearchParams(); return ( <main> <p>{searchParams.get('key')}</p> <button onClick={() => setSearchParams({ key: 'value' })}> Set Param </button> <button onClick={() => setSearchParams((prevSearchParams) => ({ ...Object.fromEntries(prevSearchParams), array: ['value_1', 'value_2'], // Array is supported just like react-router. })) } > Merge Params </button> </main> ); }
If interested, I could open up a PR to add this as an official helper util :)
Reacted by Hugo GresseReacted by Hugo GresseReacted by Dmitry Ivakhnenko, Dan and Hugo GresseIf interested, I could open up a PR to add this as an official helper util :)
Cool, that could be a nice feature to have in v3. We're preparing the first release candidate right now, I will let you know once it is ready, so you can start working on a PR.
Reacted by Kwan Jun Wen- addedV3Features that won't be released in v2, but planned for the next major releaseFeatures that won't be released in v2, but planned for the next major release
on Nov 14, 2023 The RC is out now. There is no
useSearchParamsyet, but we added it to the roadmap for v3.1Reacted by Kwan Jun WenReacted by Aarav Khokhar- changed the title
[-]Need support for search params.[/-][+]`useSearchParams` for extracting search parameters[/+]on Nov 22, 2023 - changed the title
[-]`useSearchParams` for extracting search parameters[/-][+]`useSearchParams` for extracting and modifying search parameters[/+]on Nov 22, 2023 I've created a PR for this feature based on my understanding (I am no expert in Routing 👀).
Any feedbacks are welcomed! @molefrogi find it quite often that you might want to replace searchParam but you don't really want to trigger a re-render in same component!
useSearch will trigger re-render everytime; infact current implementaion trigger re-render even if params/href didnot change at all. (it re-render on any navigation event)
a nice js helper to manipulate searchParams is more than enough more most cases imo
export function replaceSearchParam(qs: Record<string,string|number|boolean|null>, replace=false){ const params = new URLSearchParams(location.search); for (const [key, value] of Object.entries(qs)) { if(value === null){ params.delete(key); } else { params.set(key, `${value}`); } } if(replace){ history.replaceState(null, "", `${location.pathname}?${params}`); } else { history.pushState(null, "", `${location.pathname}?${params}`); } }Reacted by Kwan Jun Weni find it quite often that you might want to replace searchParam but you don't really want to trigger a re-render in same component!
useSearch will trigger re-render everytime; infact current implementaion trigger re-render even if params/href didnot change at all. (it re-render on any navigation event)
a nice js helper to manipulate searchParams is more than enough more most cases imo
export function replaceSearchParam(qs: Record<string,string|number|boolean|null>, replace=false){ const params = new URLSearchParams(location.search); for (const [key, value] of Object.entries(qs)) { if(value === null){ params.delete(key); } else { params.set(key, `${value}`); } } if(replace){ history.replaceState(null, "", `${location.pathname}?${params}`); } else { history.pushState(null, "", `${location.pathname}?${params}`); } }I believe triggering a re-render upon
searchchanges is the desired behaviour. E.g, updating query?status=failedwill trigger an update on the UI and refetch API withstatus = "failed".May I know on what occasion that you would want to update the search params without re-rendering? This also mean that when you hit back on the browser, nothing happens because UI does not re-render.
- removedV3Features that won't be released in v2, but planned for the next major releaseFeatures that won't be released in v2, but planned for the next major release
on May 19, 2024
Need something like useSearchParams in React Router to handle queries.