'use client'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useMemo, useRef } from 'react'; /** * URL state for the analytical views (explore, trajectories, scatter, finder, extremes…): every meaningful * piece of view state lives in the query string so a view is shareable and survives a reload. `replace` * batches rapid updates (a dragged slider) with `router.replace` and never scrolls. * * const { get, set, url } = useUrlState(); * set({ year: 1990, indicator: 'gdp-per-capita' }) // null/undefined/'' remove the key */ import { applyPatch, type UrlPatch } from './url-params'; export { applyPatch, parseList, parseYear, type UrlPatch } from './url-params'; export function useUrlState(defaults: Record = {}) { const router = useRouter(); const pathname = usePathname(); const params = useSearchParams(); const pending = useRef({}); const timer = useRef | null>(null); const get = useCallback((key: string): string | null => params.get(key) ?? defaults[key] ?? null, [params, defaults]); const getNum = useCallback( (key: string): number | null => { const v = params.get(key) ?? defaults[key]; if (v == null || v === '') return null; const n = Number(v); return Number.isFinite(n) ? n : null; }, [params, defaults], ); const flush = useCallback(() => { const patch = pending.current; pending.current = {}; timer.current = null; const next = applyPatch(new URLSearchParams(window.location.search), patch); // Drop keys equal to their default so canonical URLs stay short. for (const [k, v] of Object.entries(defaults)) if (next.get(k) === v) next.delete(k); const s = next.toString(); router.replace(`${pathname}${s ? `?${s}` : ''}`, { scroll: false }); }, [router, pathname, defaults]); /** Merge a patch into the URL; coalesces updates within `delay` ms (default 120 — slider friendly). */ const set = useCallback( (patch: UrlPatch, delay = 120) => { pending.current = { ...pending.current, ...patch }; if (timer.current) clearTimeout(timer.current); if (delay <= 0) flush(); else timer.current = setTimeout(flush, delay); }, [flush], ); const url = useMemo(() => `${pathname}${params.toString() ? `?${params.toString()}` : ''}`, [pathname, params]); return { get, getNum, set, params, url } as const; }