UNPKG

use-query-guard

Version:

A router-agnostic query-string management hook for React with Zod validation

102 lines 3.03 kB
// src/useQueryGuard.ts import { useCallback, useMemo, useReducer, useSyncExternalStore } from "react"; import { ZodNumber } from "zod"; var browserAdapter = { getSearch: () => typeof window === "undefined" ? "" : window.location.search, setSearch: (next) => { if (typeof window === "undefined") return; const url = window.location.pathname + (next.startsWith("?") || next === "" ? next : `?${next}`) + window.location.hash; window.history.pushState(null, "", url); window.dispatchEvent(new Event("popstate")); }, subscribe: (cb) => { if (typeof window === "undefined") return () => { }; window.addEventListener("popstate", cb); return () => window.removeEventListener("popstate", cb); } }; function useQueryGuard(options) { const { resolver, preprocess, adapter = browserAdapter, mode = "pick" } = options ?? {}; const search = useSyncExternalStore( adapter.subscribe, adapter.getSearch, () => "" ); const [isReady, setReadyTrue] = useReducer(() => true, false); const { data, isError } = useMemo(() => { const usp = new URLSearchParams(search); const raw = {}; usp.forEach((value, key) => { raw[key] = preprocess ? preprocess(key, value) : value; }); setReadyTrue(); if (!resolver) { return { data: raw, isError: false }; } const converted = {}; for (const key of Object.keys(resolver.shape)) { const schema = resolver.shape[key]; const v = raw[key]; if (v === void 0) continue; converted[key] = schema instanceof ZodNumber ? Number(v) : v; } const base = Object.fromEntries( Object.keys(resolver.shape).map((k) => [k, void 0]) ); if (mode === "pick") { let anyError = false; const picked = {}; for (const [key, schema] of Object.entries(resolver.shape)) { const val = converted[key]; if (val === void 0) continue; const r = schema.safeParse(val); if (r.success) picked[key] = r.data; else anyError = true; } return { data: { ...base, ...picked }, isError: anyError }; } else { const parsed = resolver.safeParse(converted); return { data: parsed.success ? { ...base, ...parsed.data } : base, isError: !parsed.success }; } }, [search, resolver, preprocess, mode]); const updateParams = useCallback( (params) => { const usp = new URLSearchParams(adapter.getSearch()); Object.entries(params).forEach(([k, v]) => { if (v === null || v === "") { usp.delete(k); } else if (v !== void 0) { usp.set(k, String(v)); } }); usp.sort(); const next = usp.toString(); const current = adapter.getSearch().replace(/^\?/, ""); if (next === current) return; adapter.setSearch(next); }, [adapter] ); return { data, isReady, isError, updateParams }; } export { useQueryGuard }; //# sourceMappingURL=index.js.map