React router v6 useSearchParams
Tags: react, typescript
Published: 2021-12-11 15:35:41

---
title: React router v6 useSearchParams
published: true
description: How I re-wrote useSearchParams to mimic use-query-params
tags: react,typescript
---


# How I did a more powerfull useSearchParams hook

With react router v5 I was using a library called [use-query-params](https://github.com/pbeshai/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

```typescript
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`.

```typescript
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.**

```typescript
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];
}
```