react-magic-search-params
Version:
Type-safe React hook to manage URL query/search params with React Router useSearchParams.
141 lines (139 loc) • 7.28 kB
TypeScript
type CommonParams = {
page?: number;
page_size?: number;
};
type MergeParams<M, O> = {
[K in keyof M]: M[K];
} & {
[K in keyof O]?: O[K];
};
type EmptyParamValue = '' | null | undefined;
type SanitizedMandatoryParams<M> = {
[K in keyof M]: Exclude<M[K], EmptyParamValue>;
};
type SanitizedOptionalParams<O> = {
[K in keyof O as Exclude<O[K], EmptyParamValue> extends never ? never : K]?: Exclude<O[K], EmptyParamValue>;
};
type RequestSafeParams<M, O> = SanitizedMandatoryParams<M> & SanitizedOptionalParams<O>;
type ParamCodec<TValue> = {
parse?: (value: string | string[] | null, context: {
key: string;
searchParams: URLSearchParams;
}) => TValue;
serialize?: (value: TValue, context: {
key: string;
}) => string | string[] | null | undefined;
};
type ParamCodecs<TParams extends Record<string, unknown>> = Partial<{
[K in keyof TParams]: ParamCodec<TParams[K]>;
}>;
type ParamKey<TParams extends Record<string, unknown>> = Extract<keyof TParams, string>;
type ProtectedParamCodec<TValue> = true | {
parse?: (value: string | string[] | null, context: {
key: string;
searchParams: URLSearchParams;
}) => TValue;
serialize?: (value: TValue, context: {
key: string;
}) => string | string[] | null | undefined;
};
type ProtectedParams<TParams extends Record<string, unknown>> = Partial<{
[K in keyof TParams]: ProtectedParamCodec<TParams[K]>;
}>;
type CoerceParamType = 'string' | 'number' | 'boolean' | 'array';
type CoerceParams<TParams extends Record<string, unknown>> = Partial<Record<ParamKey<TParams>, CoerceParamType>>;
type PaginationStrategy<TParams extends Record<string, unknown>> = {
mode: 'page';
pageKey?: ParamKey<TParams>;
pageSizeKey?: ParamKey<TParams>;
} | {
mode: 'offset';
offsetKey?: ParamKey<TParams>;
limitKey?: ParamKey<TParams>;
} | {
mode: 'cursor';
cursorKey?: ParamKey<TParams>;
};
type ResetOnChangeRules<TParams extends Record<string, unknown>> = Partial<Record<ParamKey<TParams>, Array<ParamKey<TParams>>>>;
type UnknownParamsPolicy = 'drop' | 'preserve';
type HistoryMode = 'push' | 'replace';
type BuiltInOmitParamValue = 'all' | 'default' | 'unknown' | 'none' | 'void';
type OmitParamValue = BuiltInOmitParamValue | (string & {});
type OnChangeEvent<TParams extends Record<string, unknown>> = {
key: keyof TParams;
previousValue: unknown;
currentValue: unknown;
};
type OnChangeCallback<TParams extends Record<string, unknown>> = ((event: OnChangeEvent<TParams>) => void) | (() => void);
/**
* Interface for the configuration object that the hook receives
*/
interface UseMagicSearchParamsOptions<M extends Record<string, unknown>, O extends Record<string, unknown>> {
mandatory: M;
optional?: O;
defaultParams?: Partial<MergeParams<M, O>>;
forceParams?: Partial<MergeParams<M, O>>;
arraySerialization?: 'csv' | 'repeat' | 'brackets';
omitParamsByValues?: Array<OmitParamValue>;
coerceParams?: CoerceParams<MergeParams<M, O>>;
codecs?: ParamCodecs<MergeParams<M, O>>;
protectedParams?: ProtectedParams<MergeParams<M, O>>;
historyMode?: HistoryMode;
resetOnChange?: ResetOnChangeRules<MergeParams<M, O>>;
paginationStrategy?: PaginationStrategy<MergeParams<M, O>>;
unknownParamsPolicy?: UnknownParamsPolicy;
}
/**
Generic hook to handle search parameters in the URL
@param mandatory - Mandatory parameters (e.g., page=1, page_size=10, etc.)
@param optional - Optional parameters (e.g., order, search, etc.)
@param defaultParams - Default parameters sent in the URL on initialization
@param forceParams - Parameters forced into the URL regardless of user input
@param omitParamsByValues - Parameters omitted if they have specific values
*/
declare const useMagicSearchParams: <M extends Record<string, unknown> & CommonParams, O extends Record<string, unknown>>({ mandatory, optional, defaultParams, arraySerialization, forceParams, omitParamsByValues, coerceParams, codecs, protectedParams, historyMode, resetOnChange, paginationStrategy, unknownParamsPolicy }: UseMagicSearchParamsOptions<M, O>) => {
searchParams: URLSearchParams;
updateParams: (input?: {
newParams?: Partial<{ [K in keyof MergeParams<M, O>]: "" | MergeParams<M, O>[K] | (MergeParams<M, O>[K] extends infer T ? T extends MergeParams<M, O>[K] ? T extends (infer TItem)[] ? TItem : never : never : never); }> | ((current: MergeParams<M, O>) => Partial<{ [K in keyof MergeParams<M, O>]: "" | MergeParams<M, O>[K] | (MergeParams<M, O>[K] extends infer T ? T extends MergeParams<M, O>[K] ? T extends (infer TItem)[] ? TItem : never : never : never); }>);
keepParams?: Partial<Record<Extract<keyof M, string> | Extract<keyof O, string>, boolean>>;
historyMode?: HistoryMode;
} | ((current: MergeParams<M, O>) => {
newParams?: Partial<{ [K in keyof MergeParams<M, O>]: "" | MergeParams<M, O>[K] | (MergeParams<M, O>[K] extends infer T ? T extends MergeParams<M, O>[K] ? T extends (infer TItem)[] ? TItem : never : never : never); }> | ((current: MergeParams<M, O>) => Partial<{ [K in keyof MergeParams<M, O>]: "" | MergeParams<M, O>[K] | (MergeParams<M, O>[K] extends infer T ? T extends MergeParams<M, O>[K] ? T extends (infer TItem)[] ? TItem : never : never : never); }>);
keepParams?: Partial<Record<Extract<keyof M, string> | Extract<keyof O, string>, boolean>>;
historyMode?: HistoryMode;
} | Partial<{ [K in keyof MergeParams<M, O>]: "" | MergeParams<M, O>[K] | (MergeParams<M, O>[K] extends infer T ? T extends MergeParams<M, O>[K] ? T extends (infer TItem)[] ? TItem : never : never : never); }>)) => void;
clearParams: ({ keepMandatoryParams, historyMode: historyModeOverride }?: {
keepMandatoryParams?: boolean;
historyMode?: HistoryMode;
}) => void;
getParams: {
(options: {
convert: false;
forRequest?: boolean;
}): MergeParams<M, O>;
(options: {
convert?: true;
forRequest: true;
}): RequestSafeParams<M, O>;
(options?: {
convert?: true;
forRequest?: false;
}): MergeParams<M, O>;
(options: {
convert: boolean;
forRequest?: boolean;
}): MergeParams<M, O> | RequestSafeParams<M, O>;
};
getParam: <K extends Extract<keyof M, string> | Extract<keyof O, string>, T extends boolean = true>(key: K, options?: {
convert: T;
}) => T extends true ? MergeParams<M, O>[K] : string;
onChange: (paramName: Extract<keyof M, string> | Extract<keyof O, string>, callbacks: Array<OnChangeCallback<MergeParams<M, O>>>) => () => void;
pagination: {
mode: "page" | "offset" | "cursor";
next: (cursor?: string) => void;
prev: () => void;
reset: () => void;
setCursor: (cursor: string | null | undefined) => void;
};
};
export { type BuiltInOmitParamValue, type CoerceParamType, type CoerceParams, type HistoryMode, type OmitParamValue, type OnChangeEvent, type PaginationStrategy, type ParamCodec, type ProtectedParamCodec, type ProtectedParams, type ResetOnChangeRules, type UnknownParamsPolicy, type UseMagicSearchParamsOptions, useMagicSearchParams };