hooks/useQueryState.ts (28 lines of code) (raw):

import { useRouter } from 'next/router'; import { useEffect, useState, useCallback } from 'react'; /* Two-way synchronization between React state and URL query parameter. * @param key - Query parameter name * @param parser - Parse from URL string to type T * @param serializer - Serialize from type T to URL string */ export function useQueryState<T>( key: string, parse: (value: unknown) => T, serialize: (value: T) => string | undefined ) { const router = useRouter(); const [state, setState] = useState<T>(() => parse(router.query[key])); useEffect(() => { setState(parse(router.query[key])); }, [router.query[key], parse]); const updateState = useCallback((value: T, onComplete?: () => void) => { const currentParams = new URLSearchParams(window.location.search); const query: Record<string, string> = {}; currentParams.forEach((value, key) => { query[key] = value; }); const serializedValue = serialize(value); if (serializedValue === undefined) { delete query[key]; } else { query[key] = serializedValue; } router.replace({ pathname: router.pathname, query }, undefined, { shallow: true }).then(onComplete); }, [router, key, serialize]); return [state, updateState] as const; }