How I did a more powerfull useSearchParams hook
With react router v5 I was using a library called use-query-params.
It had this great hook called useQueryParam
which let you manage 1 query parameter in the same way as useState
worked.
One great thing about useQueryParam
is that it respects all other queries that you currently store in the url.
Meaning it only updates the value that you set out each hook the be responsible for
function SearchInput() {
const [searchTerm, setSearchTerm] = useQueryParam('q', StringParam);
return <input onChange={(event) => setSearchTerm(event.target.value)} value={searchTerm} />
}
If you then had other components that updated other url parameters like filters and so on it still kept my "q"-parameter intact.
In React router v6 they expose a hook called useSearchParams
which is great and it was really missing something like that from v5.
The only problem (that I think) is the fact that it overrides all other url parameters so you constantly have to have the entire url param object to update it with. But I want to have different component handling different parts of the url parameters.
That's why I wrote a new hook with inspiration from the use-query-param
library.
I posted the entire hook down below. I utilized serialize-query-param
library which is authored by the same person who wrote use-query-params
. The hook I wrote works in the same way as useState
.
function SearchInput() {
const [searchTerm, setSearchTerm] = useSearchParam('q', StringParam);
const changeSearchTerm = (event: React.ChangeEvent<HTMLInputElement>): void => {
setSearchTerm(event.target.value, 'replace');
// you could also use a callback function to set the value like this
setSearchTerm((oldValue) => {
// do something with oldValue if you like
return event.target.value;
}, 'replace') // replace or push to url (push is default)
}
return <input onChange={} value={searchTerm} />
}
This is the end result of the hook I wrote. It's pretty straight forward. Unfortonatly I'm using UNSAFE_NavigationContext
from react router. As far as I can tell it's okayish to use it. There are some issues on the react-router repo discussing this but as of writing this they
are probably not going to export a hook that can do what I want since they want to keep react-router lightweight but hopefully in the future they will expose this context in a more friendly way.
If you are using it in production, make sure to test it well.
import { isString } from 'lodash';
import { useContext } from 'react';
import { UNSAFE_NavigationContext, useSearchParams } from 'react-router-dom';
import { QueryParamConfig, StringParam } from 'serialize-query-params';
type NewValueType<D> = D | ((latestValue: D) => D);
type UrlUpdateType = 'replace' | 'push' | undefined;
type UseSearchParam<D, D2 = D> = [D2, (newValue: NewValueType<D>, updateType?: UrlUpdateType) => void];
export default function useSearchParam<D, D2 = D>(
name: string,
config: QueryParamConfig<D, D2> = StringParam as QueryParamConfig<any>,
): UseSearchParam<D, D2> {
const [searchParams, setSearchParams] = useSearchParams();
const { navigator } = useContext(UNSAFE_NavigationContext);
const setNewValue = (valueOrFn: NewValueType<D>, updateType?: UrlUpdateType): void => {
let newValue;
const value = searchParams.get(name);
if (typeof valueOrFn === 'function') {
// eslint-disable-next-line @typescript-eslint/ban-types
newValue = (valueOrFn as Function)(config.decode(value));
} else {
newValue = valueOrFn;
}
const encodedValue = config.encode(newValue);
const params = new URLSearchParams((navigator as any).location.search);
if (isString(encodedValue)) {
params.set(name, encodedValue);
} else {
params.delete(name);
}
setSearchParams(params, { replace: updateType === 'replace' });
};
const decodedValue = config.decode(searchParams.get(name));
return [decodedValue, setNewValue];
}
Top comments (0)