
A Simple useUrlParams Hook
Maintaining web application state in URLs has some benefits: it makes state "visible" to users, allows to easily share state, and avoids conflicts with other mechanisms like localStorage when using multiple tabs.
Relying on URL query parameters to maintain state in React may not be as straight-forward as one thinks, though. The answers to this very popular StackOverflow question fall into one of two camps:
- Relying on React Router, which can be a heavy-handed approach if one does not a full-fledged router.
- Using the browser's
URLSearchParamsinterface, which only retrieves query params once, though, while it might be desired to re-render components if the URL is changed (viaHistory.pushState()orHistory.replaceState()).
Fortunately, React 18's built-in useSyncExternalStore hook makes it possible to build a small, reactive useUrlParams hook. This is the TypeScript code:
1import { useCallback, useMemo, useSyncExternalStore } from 'react';2type PushStateParameters = Parameters<typeof window.history.pushState>;3type Listener = () => void;45let urlParams = new URLSearchParams(window.location.search);67window.history.pushState = new Proxy(window.history.pushState, {8apply: (fn, thisArg, pushStateArgs) => {9urlParams = new URLSearchParams(pushStateArgs[2]);10listeners.forEach((listener) => listener());11return fn.apply(thisArg, pushStateArgs as PushStateParameters);12},13});1415const listeners = new Set<Listener>();1617function subscribe(listener: Listener) {18listeners.add(listener);19return () => listeners.delete(listener);20}2122export function useUrlParams() {23return useSyncExternalStore(24subscribe,25useCallback(() => urlParams, [urlParams])26);27}2829export function useUrlParam(name: string) {30return useSyncExternalStore(31subscribe,32useCallback(() => urlParams.get(name), [urlParams.get(name), name])33);34}
After imports and type definitions, line 5 defines the initial state relying on the URLSearchParams interface. Lines 7 to 13 setup a Proxy object, which intercepts any calls to pushState to update urlParams and then informs all subscribed listeners to get an updated state snapshot (details in the useSyncExternalStore docs). Alternatively / in addition, the proxy could also be set up for replaceState.
Lines 15 through 20 define a Set holding listeners that subscribe to state updates, and a basic subscribe function for adding listeners to said set and removing them once they unmount.
Relying on this setup, two hooks can be defined:
useUrlParamsreturns the latestUrlSearchParamsobject instance. Components using this hook re-render whenever any query parameter is added / changed / removed viapushState.useUrlParamtakes as a single input thenameof a query parameter, and returns its string value (ornull, if it does not exist in the URL). Components using this hook re-render only when a query parameter with that given name is added / changed / removed viapushState.