@gawryco/use-shareable-state
Version:
The tiny, typed React hook for URL query string state. Transform your components into shareable, bookmarkable experiences with zero boilerplate.
298 lines (296 loc) • 12.3 kB
text/typescript
/**
* Setter signature returned by the hook methods. Mirrors React's `setState` API.
*
* Accepts either a direct value or an updater function of the previous value.
*
* @template T Value type
* @param value Either the next value or a function that derives it from the previous value
*/
type Updater<T> = (value: T | ((prev: T) => T)) => void;
/**
* Replaces the current URL's query string (without a navigation) so the page
* stays in place and the history stack receives a new state.
*
* Uses `history.replaceState` to avoid pushing a new entry unless the consumer
* opts into push behavior in a custom implementation.
*
* @param params Query parameters to apply to the current URL
*/
type HistoryAction = 'replace' | 'push';
interface NumberBuilder {
/**
* Creates a non-nullable number state bound to a query param.
*
* @param defaultValue Required default value when the param is missing/invalid
* @param opts Optional constraints and history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: number, opts?: {
min?: number;
max?: number;
step?: number;
action?: HistoryAction;
}): [number, Updater<number>];
/**
* Creates a nullable number state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional constraints and history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue?: number | null, opts?: {
min?: number;
max?: number;
step?: number;
action?: HistoryAction;
}): [number | null, Updater<number | null>];
}
interface StringBuilder {
/**
* Creates a non-nullable string state bound to a query param.
*
* @param defaultValue Required default value when the param is missing
* @param opts Optional length constraints and history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: string, opts?: {
minLength?: number;
maxLength?: number;
action?: HistoryAction;
}): [string, Updater<string>];
/**
* Creates a nullable string state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional length constraints and history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue?: string | null, opts?: {
minLength?: number;
maxLength?: number;
action?: HistoryAction;
}): [string | null, Updater<string | null>];
}
interface BooleanBuilder {
/**
* Creates a non-nullable boolean state bound to a query param.
*
* @param defaultValue Required default value when the param is missing
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: boolean, opts?: {
action?: HistoryAction;
}): [boolean, Updater<boolean>];
/**
* Creates a nullable boolean state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue?: boolean | null, opts?: {
action?: HistoryAction;
}): [boolean | null, Updater<boolean | null>];
}
interface DateBuilder {
/**
* Creates a non-nullable Date state bound to a query param.
*
* @param defaultValue Required default value when the param is missing/invalid
* @param opts Optional min/max clamping and history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: Date, opts?: {
min?: Date;
max?: Date;
action?: HistoryAction;
}): [Date, Updater<Date>];
/**
* Creates a nullable Date state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional min/max clamping and history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue?: Date | null, opts?: {
min?: Date;
max?: Date;
action?: HistoryAction;
}): [Date | null, Updater<Date | null>];
}
interface EnumBuilder<U extends string> {
/**
* Creates a non-nullable enum state bound to a query param.
*
* @param allowed Array of allowed string values
* @param defaultValue Required default value when the param is not in allowed list
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(allowed: readonly U[], defaultValue: U, opts?: {
action?: HistoryAction;
}): [U, Updater<U>];
/**
* Creates a nullable enum state bound to a query param.
*
* @param allowed Array of allowed string values
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(allowed: readonly U[], defaultValue?: U | null, opts?: {
action?: HistoryAction;
}): [U | null, Updater<U | null>];
}
interface CustomBuilder<T> {
/**
* Creates a non-nullable custom state bound to a query param.
*
* @param defaultValue Required default value when parsing fails
* @param parse Function parsing the raw string into T (return null on failure)
* @param format Function formatting T into a string
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: T, parse: (raw: string) => T | null, format: (value: T) => string, opts?: {
action?: HistoryAction;
}): [T, Updater<T>];
/**
* Creates a nullable custom state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param parse Function parsing the raw string into T (return null on failure)
* @param format Function formatting T into a string (null values result in empty string)
* @param opts Optional history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue: T | null, parse: (raw: string) => T | null, format: (value: T | null) => string, opts?: {
action?: HistoryAction;
}): [T | null, Updater<T | null>];
}
interface JsonBuilder<T> {
/**
* Creates a non-nullable JSON state bound to a query param.
*
* @param defaultValue Required default value when parsing fails
* @param opts Optional validation, omitEmpty, custom serializers, and history action
* @returns A tuple `[value, setValue]` where value is never null
*/
(defaultValue: T, opts?: {
validate?: (value: unknown) => value is T;
omitEmpty?: (value: T) => boolean;
stringify?: (value: T) => string;
parse?: (raw: string) => unknown;
action?: HistoryAction;
}): [T, Updater<T>];
/**
* Creates a nullable JSON state bound to a query param.
*
* @param defaultValue Optional default value (defaults to null)
* @param opts Optional validation, omitEmpty, custom serializers, and history action
* @returns A tuple `[value, setValue]` where value can be null
*/
optional(defaultValue?: T | null, opts?: {
validate?: (value: unknown) => value is T;
omitEmpty?: (value: T) => boolean;
stringify?: (value: T) => string;
parse?: (raw: string) => unknown;
action?: HistoryAction;
}): [T | null, Updater<T | null>];
}
/**
* Public API: returns builder methods for creating typed query‑state pairs.
*
* Pattern:
* const [value, setValue] = useShareableState('key').number(123); // non-nullable
* const [value, setValue] = useShareableState('key').number().optional(); // nullable
*
* Available builders:
* - number(defaultValue): number (non-nullable) | number().optional(): number | null
* - string(defaultValue): string (non-nullable) | string().optional(): string | null
* - boolean(defaultValue): boolean (non-nullable) | boolean().optional(): boolean | null
* - date(defaultValue): Date (non-nullable) | date().optional(): Date | null
* - enum<U>(allowed, defaultValue): U (non-nullable) | enum<U>().optional(allowed): U | null
* - custom<T>(defaultValue, parse, format): T (non-nullable) | custom<T>().optional(): T | null
* - json<T>(defaultValue): T (non-nullable) | json<T>().optional(): T | null
*/
declare function useShareableState(key: string): {
/**
* Number state builder. Use .number(defaultValue) for non-nullable or .number().optional() for nullable.
*
* @example
* const [count, setCount] = useShareableState('count').number(0); // non-nullable
* const [optional, setOptional] = useShareableState('opt').number().optional(); // nullable
*/
readonly number: NumberBuilder;
/**
* String state builder. Use .string(defaultValue) for non-nullable or .string().optional() for nullable.
*
* @example
* const [name, setName] = useShareableState('name').string(''); // non-nullable
* const [optional, setOptional] = useShareableState('opt').string().optional(); // nullable
*/
readonly string: (defaultValue?: string, opts?: {
minLength?: number;
maxLength?: number;
action?: HistoryAction;
}) => [string, Updater<string>] | StringBuilder;
/**
* Boolean state builder. Use .boolean(defaultValue) for non-nullable or .boolean().optional() for nullable.
*
* @example
* const [active, setActive] = useShareableState('active').boolean(false); // non-nullable
* const [optional, setOptional] = useShareableState('opt').boolean().optional(); // nullable
*/
readonly boolean: BooleanBuilder;
/**
* Date state builder. Use .date(defaultValue) for non-nullable or .date().optional() for nullable.
*
* @example
* const [start, setStart] = useShareableState('start').date(new Date()); // non-nullable
* const [optional, setOptional] = useShareableState('opt').date().optional(); // nullable
*/
readonly date: (defaultValue?: Date, opts?: {
min?: Date;
max?: Date;
action?: HistoryAction;
}) => [Date, Updater<Date>] | DateBuilder;
/**
* Enum state builder. Binds a string literal union (enum-like) to a query param.
*
* @template U extends string
* @example
* type Theme = 'light' | 'dark';
* const [theme, setTheme] = useShareableState('theme').enum<Theme>(['light','dark'], 'light'); // non-nullable
* const [optional, setOptional] = useShareableState('opt').enum<Theme>().optional(['light','dark']); // nullable
*/
readonly enum: <U extends string>(allowed?: readonly U[], defaultValue?: U, opts?: {
action?: HistoryAction;
}) => [U, Updater<U>] | EnumBuilder<U>;
/**
* Custom state builder. Provide your own parse/format functions.
*
* @template T
* @example
* const [ids, setIds] = useShareableState('ids').custom<number[]>([], parse, format); // non-nullable
* const [optional, setOptional] = useShareableState('opt').custom<number[]>().optional(null, parse, format); // nullable
*/
readonly custom: <T>() => CustomBuilder<T>;
/**
* JSON state builder. Binds a JSON-serializable value to a query param.
*
* @template T
* @example
* const [data, setData] = useShareableState('data').json<{q: string}>({q: ''}); // non-nullable
* const [optional, setOptional] = useShareableState('opt').json<{q: string}>().optional(); // nullable
*/
readonly json: <T>(defaultValue?: T, opts?: {
validate?: (value: unknown) => value is T;
omitEmpty?: (value: T) => boolean;
stringify?: (value: T) => string;
parse?: (raw: string) => unknown;
action?: HistoryAction;
}) => [T, Updater<T>] | JsonBuilder<T>;
};
export { useShareableState };