import { decodeSearch, encodeSearch, mergeObject, type s, parse } from "bknd/utils"; import { isEqual, transform } from "lodash-es"; import { useLocation, useSearch as useWouterSearch } from "wouter"; import { useEffect, useMemo, useState } from "react"; export type UseSearchOptions = { defaultValue?: Partial>; beforeEncode?: (search: Partial>) => object; }; export function useSearch( schema: Schema, options?: UseSearchOptions, ) { const searchString = useWouterSearch(); const [location, navigate] = useLocation(); const defaults = useMemo(() => { return mergeObject( // @ts-ignore schema.template({ withOptional: true }), options?.defaultValue ?? {}, ); }, [JSON.stringify({ schema, dflt: options?.defaultValue })]); const [value, setValue] = useState>(defaults); useEffect(() => { const initial = searchString.length > 0 ? decodeSearch(searchString) : (options?.defaultValue ?? {}); const v = parse(schema, Object.assign({}, defaults, initial)) as any; setValue(v); }, [searchString, JSON.stringify(options?.defaultValue), location]); function set>>(update: Update): void { const search = getWithoutDefaults(Object.assign({}, value, update), defaults); const prepared = options?.beforeEncode?.(search) ?? search; const encoded = encodeSearch(prepared, { encode: false }); navigate(location + (encoded.length > 0 ? "?" + encoded : "")); } return { value: value as Required>, set, }; } function getWithoutDefaults(value: object, defaultValue: object) { return transform( value as any, (result, value, key) => { if (defaultValue && isEqual(value, defaultValue[key])) return; result[key] = value; }, {} as object, ); }