UNPKG

nuqs

Version:

Type-safe search params state manager for React - Like useState, but stored in the URL query string

360 lines (359 loc) 13.1 kB
import { TransitionStartFunction } from "react"; import { StandardSchemaV1 } from "@standard-schema/spec"; //#region src/defs.d.ts type SearchParams = Record<string, string | string[] | undefined>; type HistoryOptions = "replace" | "push"; type LimitUrlUpdates = { method: "debounce"; timeMs: number; } | { method: "throttle"; timeMs: number; }; type Options = { /** * How the query update affects page history * * `push` will create a new history entry, allowing to use the back/forward * buttons to navigate state updates. * `replace` (default) will keep the current history point and only replace * the query string. */ history?: HistoryOptions; /** * Scroll to top after a query state update * * Defaults to `false`, unlike the Next.js router page navigation methods. */ scroll?: boolean; /** * Shallow mode (true by default) keeps query states update client-side only, * meaning there won't be calls to the server. * * Setting it to `false` will trigger a network request to the server with * the updated querystring. */ shallow?: boolean; /** * Maximum amount of time (ms) to wait between updates of the URL query string. * * This is to alleviate rate-limiting of the Web History API in browsers, * and defaults to 50ms. Safari requires a higher value of around 120ms. * * Note: the value will be limited to a minimum of 50ms, anything lower * will not have any effect. * * @deprecated use `limitUrlUpdates: { 'method': 'throttle', timeMs: number }` * or use the shorthand: * ```ts * import { throttle } from 'nuqs' * * limitUrlUpdates: throttle(100) // time in ms * ``` */ throttleMs?: number; /** * Limit the rate of URL updates to prevent spamming the browser history, * and the server if `shallow: false`. * * This is to alleviate rate-limiting of the Web History API in browsers, * and defaults to 50ms. Safari requires a higher value of around 120ms. * * Note: the value will be limited to a minimum of 50ms, anything lower * will not have any effect. * * If both `throttleMs` and `limitUrlUpdates` are set, `limitUrlUpdates` will * take precedence. */ limitUrlUpdates?: LimitUrlUpdates; /** * In RSC frameworks, opt-in to observing Server Component loading states when * doing non-shallow updates by passing a `startTransition` from the * `React.useTransition()` hook. * * In other frameworks, navigation events triggered by a query update can also * be wrapped in a transition this way (e.g. `React.startTransition`). */ startTransition?: TransitionStartFunction; /** * Clear the key-value pair from the URL query string when setting the state * to the default value. * * Defaults to `true` to keep URLs clean. * * Set it to `false` to keep backwards-compatiblity when the default value * changes (prefer explicit URLs whose meaning don't change). */ clearOnDefault?: boolean; }; type Nullable<T> = { [K in keyof T]: T[K] | null } & {}; /** * Helper type to define and reuse urlKey options to rename search params keys * * Usage: * ```ts * import { type UrlKeys } from 'nuqs' // or 'nuqs/server' * * export const coordinatesSearchParams = { * latitude: parseAsFloat.withDefault(0), * longitude: parseAsFloat.withDefault(0), * } * export const coordinatesUrlKeys: UrlKeys<typeof coordinatesSearchParams> = { * latitude: 'lat', * longitude: 'lng', * } * * // Later in the code: * useQueryStates(coordinatesSearchParams, { * urlKeys: coordinatesUrlKeys * }) * createSerializer(coordinatesSearchParams, { * urlKeys: coordinatesUrlKeys * }) * createSearchParamsCache(coordinatesSearchParams, { * urlKeys: coordinatesUrlKeys * }) * ``` */ type UrlKeys<Parsers extends Record<string, any>> = Partial<Record<keyof Parsers, string>>; //#endregion //#region src/parsers.d.ts type Require<T, Keys extends keyof T> = Pick<Required<T>, Keys> & Omit<T, Keys>; type Parser<T> = { /** * Convert a query string value into a state value. * * If the string value does not represent a valid state value, * the parser should return `null`. Throwing an error is also supported. */ parse: (value: string) => T | null; /** * Render the state value into a query string value. */ serialize?: (value: T) => string; /** * Check if two state values are equal. * * This is used when using the `clearOnDefault` value, to compare the default * value with the set value. * * It makes sense to provide this function when the state value is an object * or an array, as the default referential equality check will not work. */ eq?: (a: T, b: T) => boolean; }; type ParserBuilder<T> = Required<Parser<T>> & Options & { /** * Set history type, shallow routing and scroll restoration options * at the hook declaration level. * * Note that you can override those options in individual calls to the * state updater function. */ withOptions<This>(this: This, options: Options): This; /** * Specifying a default value makes the hook state non-nullable when the * query is missing from the URL: the default value is returned instead * of `null`. * * Setting the state to the default value¹ will clear the query string key * from the URL, unless `clearOnDefault` is set to `false`. * * Setting the state to `null` will always clear the query string key * from the URL, and return the default value. * * ¹: Equality is checked with the parser's `eq` function, or referential * equality if not provided. * * @param defaultValue */ withDefault(this: ParserBuilder<T>, defaultValue: NonNullable<T>): Omit<ParserBuilder<T>, "parseServerSide"> & { readonly defaultValue: NonNullable<T>; /** * Use the parser in Server Components * * `parse` is intended to be used only by the hook, but you can use this * method to hydrate query values on server-side rendered pages. * See the `server-side-parsing` demo for an example. * * Note that when multiple queries are presented to the parser * (eg: `/?a=1&a=2`), only the **first** will be parsed, to mimic the * behaviour of URLSearchParams: * https://url.spec.whatwg.org/#dom-urlsearchparams-get * * @param value as coming from page props * * @deprecated prefer using loaders instead, as they enforce a strong * bond between the data type and the search param key. */ parseServerSide(value: string | string[] | undefined): NonNullable<T>; }; /** * Use the parser in Server Components * * `parse` is intended to be used only by the hook, but you can use this * method to hydrate query values on server-side rendered pages. * See the `server-side-parsing` demo for an example. * * Note that when multiple queries are presented to the parser * (eg: `/?a=1&a=2`), only the **first** will be parsed, to mimic the * behaviour of URLSearchParams: * https://url.spec.whatwg.org/#dom-urlsearchparams-get * * @param value as coming from page props * * @deprecated prefer using loaders instead, as they enforce a strong * bond between the data type and the search param key. */ parseServerSide(value: string | string[] | undefined): T | null; }; /** * 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. */ declare function createParser<T>(parser: Require<Parser<T>, "parse" | "serialize">): ParserBuilder<T>; declare const parseAsString: ParserBuilder<string>; declare const parseAsInteger: ParserBuilder<number>; declare const parseAsIndex: ParserBuilder<number>; declare const parseAsHex: ParserBuilder<number>; declare const parseAsFloat: ParserBuilder<number>; declare const parseAsBoolean: ParserBuilder<boolean>; /** * Querystring encoded as the number of milliseconds since epoch, * and returned as a Date object. */ declare const parseAsTimestamp: ParserBuilder<Date>; /** * Querystring encoded as an ISO-8601 string (UTC), * and returned as a Date object. */ declare const parseAsIsoDateTime: ParserBuilder<Date>; /** * 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. */ declare const parseAsIsoDate: ParserBuilder<Date>; /** * 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 */ declare function parseAsStringEnum<Enum extends string>(validValues: Enum[]): ParserBuilder<Enum>; /** * 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 */ declare function parseAsStringLiteral<const Literal extends string>(validValues: readonly Literal[]): ParserBuilder<Literal>; /** * 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 */ declare function parseAsNumberLiteral<const Literal extends number>(validValues: readonly Literal[]): ParserBuilder<Literal>; /** * 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 */ declare function parseAsJson<T>(validator: ((value: unknown) => T | null) | StandardSchemaV1<T>): ParserBuilder<T>; /** * 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 ',') */ declare function parseAsArrayOf<ItemType>(itemParser: Parser<ItemType>, separator?: string): ParserBuilder<ItemType[]>; type inferSingleParserType<Parser> = Parser extends ParserBuilder<infer Value> & { defaultValue: infer Value; } ? Value : Parser extends ParserBuilder<infer Value> ? Value | null : never; type inferParserRecordType<Map extends Record<string, ParserBuilder<any>>> = { [Key in keyof Map]: inferSingleParserType<Map[Key]> } & {}; /** * Type helper to extract the underlying returned data type of a parser * or of an object describing multiple parsers and their associated keys. * * Usage: * * ```ts * import { type inferParserType } from 'nuqs' // or 'nuqs/server' * * const intNullable = parseAsInteger * const intNonNull = parseAsInteger.withDefault(0) * * inferParserType<typeof intNullable> // number | null * inferParserType<typeof intNonNull> // number * * const parsers = { * a: parseAsInteger, * b: parseAsBoolean.withDefault(false) * } * * inferParserType<typeof parsers> * // { a: number | null, b: boolean } * ``` */ type inferParserType<Input> = Input extends ParserBuilder<any> ? inferSingleParserType<Input> : Input extends Record<string, ParserBuilder<any>> ? inferParserRecordType<Input> : never; type ParserWithOptionalDefault<T> = ParserBuilder<T> & { defaultValue?: T; }; type ParserMap = Record<string, ParserWithOptionalDefault<any>>; //#endregion export { HistoryOptions, LimitUrlUpdates, Nullable, Options, Parser, ParserBuilder, ParserMap, ParserWithOptionalDefault, SearchParams, UrlKeys, createParser, inferParserType, parseAsArrayOf, parseAsBoolean, parseAsFloat, parseAsHex, parseAsIndex, parseAsInteger, parseAsIsoDate, parseAsIsoDateTime, parseAsJson, parseAsNumberLiteral, parseAsString, parseAsStringEnum, parseAsStringLiteral, parseAsTimestamp }; //# sourceMappingURL=parsers-gDSkX8OH.d.ts.map