Skip to content

Instantly share code, notes, and snippets.

@darioielardi
Last active January 28, 2022 12:29
Show Gist options
  • Save darioielardi/ce5854a8b09e4f53d5ee46f33784af5b to your computer and use it in GitHub Desktop.
Save darioielardi/ce5854a8b09e4f53d5ee46f33784af5b to your computer and use it in GitHub Desktop.
nextjs uncontrolled search params hook
import { useRouter } from 'next/router';
import { ParsedUrlQuery, ParsedUrlQueryInput } from 'querystring';
import { useEffect, useMemo, useState } from 'react';
// common defs
export const numParam: ParamDef<number> = {
encode: (value) => value.toString(),
decode: (value) => (typeof value === 'string' && value ? parseInt(value, 10) : undefined),
};
export const boolParam: ParamDef<boolean> = {
get: (value) => value ?? undefined,
set: (value) => value || undefined,
encode: (value) => (value ? '1' : undefined),
decode: (value) => value === '1',
};
export const tripleBoolParam: ParamDef<boolean> = {
encode: (value) => (value ? '1' : '0'),
decode: (value) => {
if (typeof value !== 'string') {
return;
}
if (value === '1') {
return true;
}
if (value === '0') {
return false;
}
return undefined;
},
};
export const datesParam: ParamDef<[Date, Date]> = {
encode: (value) => value.map((date) => date.toISOString()),
decode: (value) => {
if (Array.isArray(value) && value.length === 2) {
return value.map((date) => new Date(date)) as [Date, Date];
}
return undefined;
},
};
export interface ParamDef<V> {
get?: (value: V | undefined) => V | undefined;
set?: (value: V | undefined) => V | undefined;
encode: (value: V) => ParsedUrlQueryInput[string];
decode: (value: ParsedUrlQuery[string]) => V | undefined;
}
export const useSearchParams = <V extends Record<string, unknown>>(defs: {
[K in keyof V]: ParamDef<V[K]>;
}): { params: Partial<V>; setParams: (value: Partial<V>) => void; resetParams: () => void } => {
const router = useRouter();
const [_params, _setParams] = useState<Partial<V>>(() => {
const params: V = {} as V;
for (const [key, def] of Object.entries(defs)) {
const value = router.query[key];
if (typeof value !== 'undefined') {
params[key as keyof V] = def.decode(value);
}
}
return params;
});
useEffect(() => {
const query: ParsedUrlQueryInput = {
...router.query,
};
for (const [key, def] of Object.entries(defs)) {
const value = _params[key as keyof V];
if (typeof value === 'undefined') {
delete query[key];
} else {
query[key] = def.encode(value);
}
}
router.replace({ query }, { query }, { shallow: true, scroll: false });
}, [_params]);
const params = useMemo(() => {
const params: V = {} as V;
for (const [key, def] of Object.entries(defs)) {
const value = _params[key as keyof V];
params[key as keyof V] = typeof def.get !== 'undefined' ? def.get(value) : value;
}
return params;
}, [_params]);
const setParams = (value: Partial<V>) => {
const newValue: Partial<V> = {
..._params,
};
for (const [key, val] of Object.entries(value)) {
const k = key as keyof V;
if (typeof defs[k].set !== 'undefined') {
newValue[k] = defs[k].set!(val);
} else {
newValue[k] = val;
}
}
_setParams({ ...newValue });
};
const resetParams = () => {
_setParams({});
};
return { params, setParams, resetParams };
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment