nuqs
Version:
721 lines (720 loc) • 26.3 kB
JavaScript
"use client";
import { c as debounce, i as createEmitter, l as defaultRateLimit, n as useQueuedQueries, o as isAbsentFromUrl, r as globalThrottleQueue, s as write, t as debounceController, u as throttle } from "./debounce-DPoC4s1g.js";
import { i as warn, t as debug } from "./debug-CH5E3t0A.js";
import { a as useAdapterProcessUrlSearchParams, c as renderQueryString, i as useAdapterDefaultOptions, o as globalSingleton, r as useAdapter } from "./context-Mu913OAK.js";
import { t as compareQuery } from "./compare-DGz2TUZl.js";
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
//#region src/lib/url-keys.ts
function getOwn(object, key) {
return Object.hasOwn(object, key) ? object[key] : void 0;
}
function getUrlKey(urlKeys, key) {
return getOwn(urlKeys, key) ?? key;
}
//#endregion
//#region src/loader.ts
function createLoader(parsers, { urlKeys = {} } = {}) {
function loadSearchParams(input, { strict = false } = {}) {
if (input instanceof Promise) return input.then((i) => loadSearchParams(i, { strict }));
const searchParams = extractSearchParams(input);
const result = {};
for (const [key, parser] of Object.entries(parsers)) {
const urlKey = getUrlKey(urlKeys, key);
const query = parser.type === "multi" ? searchParams.getAll(urlKey) : searchParams.get(urlKey);
if (isAbsentFromUrl(query)) {
result[key] = parser.defaultValue ?? null;
continue;
}
let parsedValue;
try {
parsedValue = parser.parse(query);
} catch (error) {
if (strict) throw new Error(`[nuqs] Error while parsing query \`${query}\` for key \`${key}\`: ${error}`);
parsedValue = null;
}
if (strict && parsedValue === null) throw new Error(`[nuqs] Failed to parse query \`${query}\` for key \`${key}\` (got null)`);
result[key] = parsedValue ?? parser.defaultValue ?? null;
}
return result;
}
return loadSearchParams;
}
function extractSearchParams(input) {
try {
if (input instanceof Request) return input.url ? new URL(input.url).searchParams : new URLSearchParams();
if (input instanceof URL) return input.searchParams;
if (input instanceof URLSearchParams) return input;
if (typeof input === "object") {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(input)) if (Array.isArray(value)) for (const v of value) searchParams.append(key, v);
else if (value !== void 0) searchParams.set(key, value);
return searchParams;
}
if (typeof input === "string") {
if (URL.canParse(input)) return new URL(input).searchParams;
return new URLSearchParams(input);
}
} catch {}
return new URLSearchParams();
}
//#endregion
//#region src/lib/safe-parse.ts
function safeParse(parser, value, key) {
try {
return parser(value);
} catch (error) {
if (key) warn(25, value, error, key);
else warn(24, value, error);
return null;
}
}
//#endregion
//#region src/parsers.ts
/**
* Wrap a set of parse/serialize functions into a builder pattern parser
* you can pass to one of the hooks, making its default value type safe.
*/
function createParser(parser) {
function parseServerSideNullable(value) {
if (typeof value === "undefined") return null;
let str = "";
if (Array.isArray(value)) {
if (value[0] === void 0) return null;
str = value[0];
}
if (typeof value === "string") str = value;
return safeParse(parser.parse, str);
}
return {
type: "single",
eq: (a, b) => a === b,
...parser,
parseServerSide: parseServerSideNullable,
withDefault(defaultValue) {
return {
...this,
defaultValue,
parseServerSide(value) {
return parseServerSideNullable(value) ?? defaultValue;
}
};
},
withOptions(options) {
return {
...this,
...options
};
}
};
}
function createMultiParser(parser) {
function parseServerSideNullable(value) {
if (typeof value === "undefined") return null;
return safeParse(parser.parse, Array.isArray(value) ? value : [value]);
}
return {
type: "multi",
eq: (a, b) => a === b,
...parser,
parseServerSide: parseServerSideNullable,
withDefault(defaultValue) {
return {
...this,
defaultValue,
parseServerSide(value) {
return parseServerSideNullable(value) ?? defaultValue;
}
};
},
withOptions(options) {
return {
...this,
...options
};
}
};
}
const parseAsString = createParser({
parse: (v) => v,
serialize: String
});
const parseAsInteger = createParser({
parse: (v) => {
const int = parseInt(v);
return int == int ? int : null;
},
serialize: (v) => "" + Math.round(v)
});
const parseAsIndex = createParser({
parse: (v) => {
const int = parseInt(v);
return int == int ? int - 1 : null;
},
serialize: (v) => "" + Math.round(v + 1)
});
const parseAsHex = createParser({
parse: (v) => {
const int = parseInt(v, 16);
return int == int ? int : null;
},
serialize: (v) => {
const hex = Math.round(v).toString(16);
return hex < "0" || !(hex.length & 1) ? hex : "0" + hex;
}
});
const parseAsFloat = createParser({
parse: (v) => {
const float = parseFloat(v);
return float == float ? float : null;
},
serialize: String
});
const parseAsBoolean = createParser({
parse: (v) => v.toLowerCase() === "true",
serialize: String
});
function compareDates(a, b) {
return a.valueOf() === b.valueOf();
}
/**
* Querystring encoded as the number of milliseconds since epoch,
* and returned as a Date object.
*/
const parseAsTimestamp = createParser({
parse: (v) => {
const date = new Date(parseInt(v));
return date.valueOf() == date.valueOf() ? date : null;
},
serialize: (v) => "" + v.valueOf(),
eq: compareDates
});
/**
* Querystring encoded as an ISO-8601 string (UTC),
* and returned as a Date object.
*/
const parseAsIsoDateTime = createParser({
parse: (v) => {
const date = new Date(v);
return date.valueOf() == date.valueOf() ? date : null;
},
serialize: (v) => v.toISOString(),
eq: compareDates
});
/**
* Querystring encoded as an ISO-8601 string (UTC)
* without the time zone offset, and returned as
* a Date object.
*
* The Date is parsed without the time zone offset,
* making it at 00:00:00 UTC.
*/
const parseAsIsoDate = createParser({
parse: (v) => {
const date = new Date(v.slice(0, 10));
return date.valueOf() == date.valueOf() ? date : null;
},
serialize: (v) => v.toISOString().slice(0, 10),
eq: compareDates
});
/**
* String-based enums provide better type-safety for known sets of values.
* You will need to pass the parseAsStringEnum function a list of your enum values
* in order to validate the query string. Anything else will return `null`,
* or your default value if specified.
*
* Example:
* ```ts
* enum Direction {
* up = 'UP',
* down = 'DOWN',
* left = 'LEFT',
* right = 'RIGHT'
* }
*
* const [direction, setDirection] = useQueryState(
* 'direction',
* parseAsStringEnum<Direction>(Object.values(Direction)) // pass a list of allowed values
* .withDefault(Direction.up)
* )
* ```
*
* Note: the query string value will be the value of the enum, not its name
* (example above: `direction=UP`).
*
* @param validValues The values you want to accept
*/
function parseAsStringEnum(validValues) {
return parseAsStringLiteral(validValues);
}
/**
* String-based literals provide better type-safety for known sets of values.
* You will need to pass the parseAsStringLiteral function a list of your string values
* in order to validate the query string. Anything else will return `null`,
* or your default value if specified.
*
* Example:
* ```ts
* const colors = ["red", "green", "blue"] as const
*
* const [color, setColor] = useQueryState(
* 'color',
* parseAsStringLiteral(colors) // pass a readonly list of allowed values
* .withDefault("red")
* )
* ```
*
* @param validValues The values you want to accept
*/
function parseAsStringLiteral(validValues) {
return createParser({
parse: (query) => {
const asConst = query;
return validValues.includes(asConst) ? asConst : null;
},
serialize: String
});
}
/**
* Number-based literals provide better type-safety for known sets of values.
* You will need to pass the parseAsNumberLiteral function a list of your number values
* in order to validate the query string. Anything else will return `null`,
* or your default value if specified.
*
* Example:
* ```ts
* const diceSides = [1, 2, 3, 4, 5, 6] as const
*
* const [side, setSide] = useQueryState(
* 'side',
* parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values
* .withDefault(4)
* )
* ```
*
* @param validValues The values you want to accept
*/
function parseAsNumberLiteral(validValues) {
return createParser({
parse: (query) => {
const asConst = parseFloat(query);
if (validValues.includes(asConst)) return asConst;
return null;
},
serialize: String
});
}
/**
* Encode any object shape into the querystring value as JSON.
* Note: you may want to use `useQueryStates` for finer control over
* multiple related query keys.
*
* @param runtimeParser Runtime parser (eg: Zod schema or Standard Schema) to validate after JSON.parse
*/
function parseAsJson(validator) {
return createParser({
parse: (query) => {
try {
const obj = JSON.parse(query);
if ("~standard" in validator) {
const result = validator["~standard"].validate(obj);
if (result instanceof Promise) throw new Error("[nuqs] Only synchronous Standard Schemas are supported in parseAsJson.");
return result.issues ? null : result.value;
}
return validator(obj);
} catch {
return null;
}
},
serialize: (value) => JSON.stringify(value),
eq(a, b) {
return a === b || JSON.stringify(a) === JSON.stringify(b);
}
});
}
/**
* A comma-separated list of items.
* Items are URI-encoded for safety, so they may not look nice in the URL.
*
* @param itemParser Parser for each individual item in the array
* @param separator The character to use to separate items (default ',')
*/
function parseAsArrayOf(itemParser, separator = ",") {
const itemEq = itemParser.eq ?? ((a, b) => a === b);
const encodedSeparator = encodeURIComponent(separator);
return createParser({
parse: (query) => {
if (query === "") return [];
return query.split(separator).map((item, index) => safeParse(itemParser.parse, item.replaceAll(encodedSeparator, separator), `[${index}]`)).filter((value) => value !== null && value !== void 0);
},
serialize: (values) => values.map((value) => {
return (itemParser.serialize ? itemParser.serialize(value) : String(value)).replaceAll(separator, encodedSeparator);
}).join(separator),
eq(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
return a.every((value, index) => itemEq(value, b[index]));
}
});
}
function parseAsNativeArrayOf(itemParser) {
const itemEq = itemParser.eq ?? ((a, b) => a === b);
return createMultiParser({
parse: (query) => {
const parsed = query.map((item, index) => safeParse(itemParser.parse, item, `[${index}]`)).filter((value) => value !== null && value !== void 0);
if (parsed.length > 0) return parsed;
return query.length === 1 && query[0] === "" ? [] : null;
},
serialize: (values) => {
return (Array.isArray(values) ? values : [values]).flatMap((value) => {
const serialized = itemParser.serialize?.(value) ?? String(value);
return typeof serialized === "string" ? [serialized] : [...serialized];
});
},
eq(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
return a.every((value, index) => itemEq(value, b[index]));
}
}).withDefault([]);
}
//#endregion
//#region src/serializer.ts
function createSerializer(parsers, { clearOnDefault = true, urlKeys = {}, processUrlSearchParams } = {}) {
function serialize(arg1BaseOrValues, arg2values = {}) {
let [base, search, hash] = isBase(arg1BaseOrValues) ? splitBase(arg1BaseOrValues) : [
"",
new URLSearchParams(),
""
];
const values = isBase(arg1BaseOrValues) ? arg2values : arg1BaseOrValues;
if (values === null) {
for (const key of Object.keys(parsers)) {
const urlKey = getUrlKey(urlKeys, key);
search.delete(urlKey);
}
if (processUrlSearchParams) search = processUrlSearchParams(search);
return base + renderQueryString(search) + hash;
}
for (const key of Object.keys(parsers)) {
const parser = parsers[key];
const value = getOwn(values, key);
if (value === void 0) continue;
const urlKey = getUrlKey(urlKeys, key);
const isMatchingDefault = parser.defaultValue !== void 0 && value !== null && (parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue);
if (value === null || (parser.clearOnDefault ?? clearOnDefault ?? true) && isMatchingDefault) search.delete(urlKey);
else {
const serialized = parser.serialize(value);
search = write(search, urlKey, serialized);
}
}
if (processUrlSearchParams) search = processUrlSearchParams(search);
return base + renderQueryString(search) + hash;
}
return serialize;
}
function isBase(base) {
return typeof base === "string" || base instanceof URLSearchParams || base instanceof URL;
}
function splitBase(base) {
if (typeof base === "string") {
const [pathAndSearch = "", ...hashParts] = base.split("#");
const hash = hashParts.length ? "#" + hashParts.join("#") : "";
const [path = "", ...search] = pathAndSearch.split("?");
return [
path,
new URLSearchParams(search.join("?")),
hash
];
} else if (base instanceof URLSearchParams) return [
"",
new URLSearchParams(base),
""
];
else {
const baseLength = base.href.length - base.search.length - base.hash.length;
const path = base.href.slice(0, baseLength).replace(/[?#]+$/, "");
const hash = base.hash || (base.href.endsWith("#") ? "#" : "");
return [
path,
new URLSearchParams(base.searchParams),
hash
];
}
}
//#endregion
//#region src/standard-schema.ts
function createStandardSchemaV1(parsers, { urlKeys, partialOutput = false } = {}) {
const serialize = createSerializer(parsers, { urlKeys });
const load = createLoader(parsers, { urlKeys });
return { "~standard": {
version: 1,
vendor: "nuqs",
validate(input) {
try {
const url = serialize(input);
const value = load(url, { strict: true });
if (partialOutput) {
for (const key in value) if (!(key in input)) delete value[key];
}
return { value };
} catch (error) {
return { issues: [{ message: error instanceof Error ? error.message : String(error) }] };
}
}
} };
}
//#endregion
//#region src/lib/sync.ts
const emitter = globalSingleton("sync-emitter", () => createEmitter());
//#endregion
//#region src/useQueryStates.ts
const defaultUrlKeys = {};
const omitDefaultValue = (key, value) => key === "defaultValue" ? void 0 : value;
/**
* Synchronise multiple query string arguments to React state in Next.js
*
* @param keys - An object describing the keys to synchronise and how to
* serialise and parse them.
* Use `parseAs(String|Integer|Float|...)` for quick shorthands.
* @param options - Optional history mode, shallow routing and scroll restoration options.
*/
function useQueryStates(keyMap, options = {}) {
const hookId = useId();
const defaultOptions = useAdapterDefaultOptions();
const processUrlSearchParams = useAdapterProcessUrlSearchParams();
const { history = defaultOptions?.history ?? "replace", scroll = defaultOptions?.scroll ?? false, shallow = defaultOptions?.shallow ?? true, throttleMs = defaultRateLimit.timeMs, limitUrlUpdates = defaultOptions?.limitUrlUpdates, clearOnDefault = defaultOptions?.clearOnDefault ?? true, startTransition, urlKeys = defaultUrlKeys } = options;
const stateKeys = Object.keys(keyMap).join(",");
const cachedKeyMapRef = useRef(keyMap);
const cachedKeyMap = cachedKeyMapRef.current;
const stableKeyMap = JSON.stringify(Object.entries(cachedKeyMap), omitDefaultValue) === JSON.stringify(Object.entries(keyMap), omitDefaultValue) && Object.entries(keyMap).every(([key, parser]) => {
if (cachedKeyMap[key]?.startTransition !== parser.startTransition) return false;
const previousDefault = cachedKeyMap[key]?.defaultValue;
const currentDefault = parser.defaultValue;
if (Object.is(previousDefault, currentDefault)) return true;
return previousDefault !== void 0 && currentDefault !== void 0 && parser.eq?.(previousDefault, currentDefault) === true;
}) ? cachedKeyMap : keyMap;
cachedKeyMapRef.current = stableKeyMap;
const resolvedUrlKeys = useMemo(() => Object.fromEntries(Object.keys(keyMap).map((key) => [key, getUrlKey(urlKeys, key)])), [stateKeys, JSON.stringify(urlKeys)]);
const adapter = useAdapter(Object.values(resolvedUrlKeys));
const initialSearchParams = adapter.searchParams;
const queryRef = useRef({});
const lastSyncKeyRef = useRef(null);
const committedPathnameRef = useRef(null);
const queuedQueries = useQueuedQueries(Object.values(resolvedUrlKeys));
const [internalState, setInternalState] = useState(() => parseMap(keyMap, resolvedUrlKeys, initialSearchParams, queuedQueries).state);
const stateRef = useRef(internalState);
const searchParamsSyncKey = JSON.stringify([Object.values(resolvedUrlKeys).map((key) => [key, initialSearchParams.getAll(key)]), queuedQueries]);
const reconcile = () => {
const { state, hasChanged } = parseMap(keyMap, resolvedUrlKeys, initialSearchParams, queuedQueries, queryRef.current, stateRef.current);
if (hasChanged) {
debug(1, hookId, stateKeys, state);
stateRef.current = state;
setInternalState(state);
}
return hasChanged;
};
const keysChanged = Object.keys(queryRef.current).join("&") !== Object.values(resolvedUrlKeys).join("&");
const onCommittedPathname = committedPathnameRef.current === null || committedPathnameRef.current === (adapter.pathname ?? location.pathname);
let didReconcileState = false;
if (keysChanged || onCommittedPathname && lastSyncKeyRef.current !== searchParamsSyncKey) {
lastSyncKeyRef.current = searchParamsSyncKey;
didReconcileState = reconcile();
if (keysChanged) queryRef.current = Object.fromEntries(Object.entries(resolvedUrlKeys).map(([key, urlKey]) => {
return [urlKey, keyMap[key]?.type === "multi" ? initialSearchParams.getAll(urlKey) : initialSearchParams.get(urlKey) ?? null];
}));
}
if (!keysChanged && !didReconcileState && onCommittedPathname && internalState !== stateRef.current) setInternalState(stateRef.current);
useEffect(() => {
committedPathnameRef.current = adapter.pathname ?? location.pathname;
reconcile();
}, [searchParamsSyncKey, adapter.pathname]);
useEffect(() => {
const handlers = Object.keys(keyMap).reduce((handlers, stateKey) => {
handlers[stateKey] = ({ state, query }) => {
setInternalState((currentState) => {
const urlKey = resolvedUrlKeys[stateKey];
if (Object.is(currentState[stateKey] ?? null, state)) {
debug(2, hookId, stateKeys, urlKey, state, keyMap[stateKey]?.defaultValue, stateRef.current);
return currentState;
}
stateRef.current = {
...stateRef.current,
[stateKey]: state
};
queryRef.current[urlKey] = query;
debug(3, hookId, stateKeys, urlKey, state, keyMap[stateKey]?.defaultValue, stateRef.current);
return stateRef.current;
});
};
return handlers;
}, {});
for (const stateKey of Object.keys(keyMap)) {
const urlKey = resolvedUrlKeys[stateKey];
debug(4, hookId, urlKey, stateKeys);
emitter.on(urlKey, handlers[stateKey]);
}
return () => {
for (const stateKey of Object.keys(keyMap)) {
const urlKey = resolvedUrlKeys[stateKey];
debug(5, hookId, urlKey, stateKeys);
emitter.off(urlKey, handlers[stateKey]);
}
};
}, [stateKeys, resolvedUrlKeys]);
const update = useCallback((stateUpdater, callOptions = {}) => {
const nullMap = Object.fromEntries(Object.keys(stableKeyMap).map((key) => [key, null]));
const newState = typeof stateUpdater === "function" ? stateUpdater(applyDefaultValues(stateRef.current, stableKeyMap)) ?? nullMap : stateUpdater ?? nullMap;
debug(6, hookId, stateKeys, newState);
let returnedPromise = void 0;
let maxDebounceTime = 0;
let doFlush = false;
const debounceAborts = [];
for (let [stateKey, value] of Object.entries(newState)) {
const parser = getOwn(stableKeyMap, stateKey);
const urlKey = resolvedUrlKeys[stateKey];
if (!parser || urlKey === void 0 || value === void 0) continue;
if ((callOptions.clearOnDefault ?? parser.clearOnDefault ?? clearOnDefault) && value !== null && parser.defaultValue !== void 0 && (parser.eq ?? ((a, b) => a === b))(value, parser.defaultValue)) value = null;
const query = value === null ? null : (parser.serialize ?? String)(value);
emitter.emit(urlKey, {
state: value,
query
});
const update = {
key: urlKey,
query,
options: {
history: callOptions.history ?? parser.history ?? history,
shallow: callOptions.shallow ?? parser.shallow ?? shallow,
scroll: callOptions.scroll ?? parser.scroll ?? scroll,
startTransition: callOptions.startTransition ?? parser.startTransition ?? startTransition
}
};
const resolvedLimitUrlUpdates = callOptions.limitUrlUpdates ?? parser.limitUrlUpdates ?? limitUrlUpdates;
if (resolvedLimitUrlUpdates?.method === "debounce") {
const timeMs = resolvedLimitUrlUpdates.timeMs ?? defaultRateLimit.timeMs;
const debouncedPromise = debounceController.push(update, timeMs, adapter, processUrlSearchParams);
if (maxDebounceTime < timeMs) {
returnedPromise = debouncedPromise;
maxDebounceTime = timeMs;
}
} else {
const timeMs = resolvedLimitUrlUpdates?.timeMs ?? callOptions.throttleMs ?? parser.throttleMs ?? throttleMs;
debounceAborts.push(debounceController.abort(urlKey));
globalThrottleQueue.push(update, timeMs);
doFlush = true;
}
}
const globalPromise = debounceAborts.reduce((previous, fn) => fn(previous), doFlush ? globalThrottleQueue.flush(adapter, processUrlSearchParams) : globalThrottleQueue.getPendingPromise(adapter));
return returnedPromise ?? globalPromise;
}, [
stateKeys,
history,
shallow,
scroll,
throttleMs,
limitUrlUpdates?.method,
limitUrlUpdates?.timeMs,
startTransition,
clearOnDefault,
stableKeyMap,
resolvedUrlKeys,
adapter.updateUrl,
adapter.getSearchParamsSnapshot,
adapter.rateLimitFactor,
processUrlSearchParams
]);
return [useMemo(() => applyDefaultValues(internalState, stableKeyMap), [internalState, stableKeyMap]), update];
}
function parseMap(keyMap, resolvedUrlKeys, searchParams, queuedQueries, cachedQuery, cachedState) {
let hasChanged = false;
const state = Object.entries(keyMap).reduce((out, [stateKey, parser]) => {
const urlKey = resolvedUrlKeys[stateKey];
const queuedQuery = getOwn(queuedQueries, urlKey);
const fallbackValue = parser.type === "multi" ? [] : null;
const query = queuedQuery === void 0 ? (parser.type === "multi" ? searchParams.getAll(urlKey) : searchParams.get(urlKey)) ?? fallbackValue : queuedQuery;
const cachedStateValue = cachedState && getOwn(cachedState, stateKey);
if (cachedQuery && cachedStateValue !== void 0 && compareQuery(getOwn(cachedQuery, urlKey) ?? fallbackValue, query)) {
out[stateKey] = cachedStateValue;
return out;
}
hasChanged = true;
out[stateKey] = (isAbsentFromUrl(query) ? null : safeParse(parser.parse, query, urlKey)) ?? null;
if (cachedQuery) cachedQuery[urlKey] = query;
return out;
}, {});
if (!hasChanged) {
const keyMapKeys = Object.keys(keyMap);
const cachedStateKeys = Object.keys(cachedState ?? {});
hasChanged = keyMapKeys.length !== cachedStateKeys.length || keyMapKeys.some((key) => !cachedStateKeys.includes(key));
}
return {
state,
hasChanged
};
}
function applyDefaultValues(state, keyMap) {
return Object.fromEntries(Object.keys(state).map((key) => [key, state[key] ?? keyMap[key]?.defaultValue ?? null]));
}
//#endregion
//#region src/useQueryState.ts
/**
* React state hook synchronized with a URL query string in Next.js
*
* If used without a `defaultValue` supplied in the options, and the query is
* missing in the URL, the state will be `null`.
*
* ### Behaviour with default values:
*
* _Note: the URL will **not** be updated with the default value if the query
* is missing._
*
* Setting the value to `null` will clear the query in the URL, and return
* the default value as state.
*
* Example usage:
* ```ts
* // Blog posts filtering by tag
* const [tag, selectTag] = useQueryState('tag')
* const filteredPosts = posts.filter(post => tag ? post.tag === tag : true)
* const clearTag = () => selectTag(null)
*
* // With default values
*
* const [count, setCount] = useQueryState(
* 'count',
* parseAsInteger.defaultValue(0)
* )
*
* const increment = () => setCount(oldCount => oldCount + 1)
* const decrement = () => setCount(oldCount => oldCount - 1)
* const clearCountQuery = () => setCount(null)
*
* // --
*
* const [date, setDate] = useQueryState(
* 'date',
* parseAsIsoDateTime.withDefault(new Date('2021-01-01'))
* )
*
* const setToNow = () => setDate(new Date())
* const addOneHour = () => {
* setDate(oldDate => new Date(oldDate.valueOf() + 3600_000))
* }
* ```
* @param key The URL query string key to bind to
* @param options - Parser (defines the state data type), optional default value and history mode.
*/
function useQueryState(key, options = {}) {
const { parse, type, serialize, eq, defaultValue, ...hookOptions } = options;
const [{ [key]: state }, setState] = useQueryStates({ [key]: {
parse: parse ?? ((x) => x),
type,
serialize,
eq,
defaultValue
} }, hookOptions);
return [state, useCallback((stateUpdater, callOptions = {}) => setState((old) => ({ [key]: typeof stateUpdater === "function" ? stateUpdater(old[key]) : stateUpdater }), callOptions), [key, setState])];
}
//#endregion
export { createLoader, createMultiParser, createParser, createSerializer, createStandardSchemaV1, debounce, defaultRateLimit, parseAsArrayOf, parseAsBoolean, parseAsFloat, parseAsHex, parseAsIndex, parseAsInteger, parseAsIsoDate, parseAsIsoDateTime, parseAsJson, parseAsNativeArrayOf, parseAsNumberLiteral, parseAsString, parseAsStringEnum, parseAsStringLiteral, parseAsTimestamp, throttle, useQueryState, useQueryStates };
//# sourceMappingURL=index.js.map