nuqs
Version:
Type-safe search params state manager for Next.js - Like React.useState, but stored in the URL query string
289 lines (284 loc) • 11.4 kB
text/typescript
import { TransitionStartFunction } from 'react';
type HistoryOptions = 'replace' | 'push';
type StartTransition<T> = T extends false ? TransitionStartFunction : T extends true ? never : TransitionStartFunction;
type Options<Shallow = unknown> = {
/**
* 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?: Extract<Shallow | boolean, 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 much higher value of around 340ms.
*
* Note: the value will be limited to a minimum of 50ms, anything lower
* will not have any effect.
*/
throttleMs?: number;
/**
* Opt-in to observing Server Component loading states when doing
* non-shallow updates by passing a `startTransition` from the
* `React.useTransition()` hook.
*
* Using this will set the `shallow` setting to `false` automatically.
* As a result, you can't set both `shallow: true` and `startTransition`
* in the same Options object.
*/
startTransition?: StartTransition<Shallow>;
/**
* Clear the key-value pair from the URL query string when setting the state
* to the default value.
*
* Defaults 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;
};
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, Shallow>(this: This, options: Options<Shallow>): This;
/**
* Specifying a default value makes the hook state non-nullable when the
* query is missing from the URL.
*
* Note: if you wish to specify options as well, you need to call
* `withOptions` **before** `withDefault`.
*
* @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
*/
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
*/
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 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>;
/**
* 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<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<Literal extends number>(validValues: readonly Literal[]): ParserBuilder<Literal>;
/**
* Encode any object shape into the querystring value as JSON.
* Value is URI-encoded for safety, so it may not look nice in the URL.
* Note: you may want to use `useQueryStates` for finer control over
* multiple related query keys.
*
* @param parser optional parser (eg: Zod schema) to validate after JSON.parse
*/
declare function parseAsJson<T>(parser?: (value: unknown) => 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 Type> & {
defaultValue: infer Type;
} ? Type : Parser extends ParserBuilder<infer Type> ? Type | 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 ExtractParserType<Parser> = Parser extends ParserBuilder<any> ? ReturnType<Parser['parseServerSide']> : never;
type Base = string | URLSearchParams | URL;
type Values<Parsers extends Record<string, ParserBuilder<any>>> = Partial<{
[K in keyof Parsers]?: ExtractParserType<Parsers[K]>;
}>;
type ParserWithOptionalDefault<T> = ParserBuilder<T> & {
defaultValue?: T;
};
declare function createSerializer<Parsers extends Record<string, ParserWithOptionalDefault<any>>>(parsers: Parsers): {
(values: Values<Parsers>): string;
(base: Base, values: Values<Parsers>): string;
};
export { type HistoryOptions as H, type Nullable as N, type Options as O, type Parser as P, type ParserBuilder as a, parseAsJson as b, parseAsArrayOf as c, createSerializer as d, createParser as e, parseAsString as f, parseAsInteger as g, parseAsHex as h, parseAsFloat as i, parseAsBoolean as j, parseAsTimestamp as k, parseAsIsoDateTime as l, parseAsStringLiteral as m, parseAsNumberLiteral as n, type inferParserType as o, parseAsStringEnum as p };