SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
2.4 KB · 59 lines typescript
Raw Blame History
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