spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1'use client';2import { usePathname, useRouter, useSearchParams } from 'next/navigation';3import { useCallback, useMemo, useRef } from 'react';45/**6 * URL state for the analytical views (explore, trajectories, scatter, finder, extremes…): every meaningful7 * piece of view state lives in the query string so a view is shareable and survives a reload. `replace`8 * batches rapid updates (a dragged slider) with `router.replace` and never scrolls.9 *10 * const { get, set, url } = useUrlState();11 * set({ year: 1990, indicator: 'gdp-per-capita' }) // null/undefined/'' remove the key12 */13import { applyPatch, type UrlPatch } from './url-params';14export { applyPatch, parseList, parseYear, type UrlPatch } from './url-params';1516export function useUrlState(defaults: Record<string, string> = {}) {17 const router = useRouter();18 const pathname = usePathname();19 const params = useSearchParams();20 const pending = useRef<UrlPatch>({});21 const timer = useRef<ReturnType<typeof setTimeout> | null>(null);2223 const get = useCallback((key: string): string | null => params.get(key) ?? defaults[key] ?? null, [params, defaults]);24 const getNum = useCallback(25 (key: string): number | null => {26 const v = params.get(key) ?? defaults[key];27 if (v == null || v === '') return null;28 const n = Number(v);29 return Number.isFinite(n) ? n : null;30 },31 [params, defaults],32 );3334 const flush = useCallback(() => {35 const patch = pending.current;36 pending.current = {};37 timer.current = null;38 const next = applyPatch(new URLSearchParams(window.location.search), patch);39 // Drop keys equal to their default so canonical URLs stay short.40 for (const [k, v] of Object.entries(defaults)) if (next.get(k) === v) next.delete(k);41 const s = next.toString();42 router.replace(`${pathname}${s ? `?${s}` : ''}`, { scroll: false });43 }, [router, pathname, defaults]);4445 /** Merge a patch into the URL; coalesces updates within `delay` ms (default 120 — slider friendly). */46 const set = useCallback(47 (patch: UrlPatch, delay = 120) => {48 pending.current = { ...pending.current, ...patch };49 if (timer.current) clearTimeout(timer.current);50 if (delay <= 0) flush();51 else timer.current = setTimeout(flush, delay);52 },53 [flush],54 );5556 const url = useMemo(() => `${pathname}${params.toString() ? `?${params.toString()}` : ''}`, [pathname, params]);57 return { get, getNum, set, params, url } as const;58}59