'use client' import { useCallback, useEffect, useId, useMemo, useReducer, useRef } from 'react' /** * Stores state in search params while bypassing Next Router. * * The purpose of this is to use search params while maintaining SSG ability, * because Next.js's `useSearchParams` forces at least the children to be * client-side rendered on static routes. * * See https://nextjs.org/docs/app/api-reference/functions/use-search-params */ const useSearchParamsShallow = () => { const EVENT_NAME = 'briven.events.packages.common.useSearchParamsShallow' const id = useId() const timeoutHandle = useRef | undefined>(undefined) const reducer = useCallback( (_: URLSearchParams, action: { target: 'int' | 'ext'; newParams: URLSearchParams }) => { clearTimeout(timeoutHandle.current) if (action.target === 'ext') { /** * Doing this in the next tick makes sure that the originating * component finishes rendering before it triggers updates to other * components. */ timeoutHandle.current = setTimeout(() => { document.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: { id } })) }) } return action.newParams }, [id] ) useEffect(() => () => clearTimeout(timeoutHandle.current), []) const [localParams, setLocalParams] = useReducer(reducer, undefined, () => new URLSearchParams()) useEffect(() => { const handler = (event: CustomEvent<{ id: string }>) => { if (event.detail.id !== id) { const globalParams = new URLSearchParams(window.location.search) setLocalParams({ target: 'int', newParams: globalParams }) } } document.addEventListener(EVENT_NAME, handler as EventListener) return () => document.removeEventListener(EVENT_NAME, handler as EventListener) }, [id]) useEffect(() => { const globalParams = new URLSearchParams(window.location.search) setLocalParams({ target: 'int', newParams: globalParams }) }, []) const has = useCallback((key: string) => localParams.has(key), [localParams]) const get = useCallback((key: string) => localParams.get(key), [localParams]) const getAll = useCallback((key: string) => localParams.getAll(key), [localParams]) const set = useCallback((key: string, value: any) => { if (typeof window === 'undefined') return const url = new URL(window.location.href) url.searchParams.set(key, value) window.history.replaceState(null, '', url) setLocalParams({ target: 'ext', newParams: url.searchParams }) }, []) const append = useCallback((key: string, value: any) => { if (typeof window === 'undefined') return const url = new URL(window.location.href) url.searchParams.append(key, value) window.history.replaceState(null, '', url) setLocalParams({ target: 'ext', newParams: url.searchParams }) }, []) const _delete = useCallback((key: string) => { if (typeof window === 'undefined') return const url = new URL(window.location.href) url.searchParams.delete(key) window.history.replaceState(null, '', url) setLocalParams({ target: 'ext', newParams: url.searchParams }) }, []) const api = useMemo( () => ({ has, get, getAll, append, set, delete: _delete, }), [has, get, getAll, append, set, _delete] ) return api } export { useSearchParamsShallow }