Created
April 18, 2023 12:00
-
-
Save pohy/f2d7ce41c274eaccc268c106ba89a8c0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useRouter } from 'next/router'; | |
export type UseQueryParamOptions<TDefaultValue> = { | |
defaultValue: TDefaultValue; | |
parse?: (value: string, defaultValue: TDefaultValue) => TDefaultValue; | |
}; | |
/** | |
* The hook retrieves a parameter from the query string using the Next.js router. | |
* Accepts an optional argument with options | |
* First option is an optional default value | |
* Second option is an optional function to parse the value | |
*/ | |
export function useQueryParam<TDefaultValue>( | |
key: string, | |
options?: UseQueryParamOptions<TDefaultValue>, | |
): TDefaultValue { | |
const router = useRouter(); | |
const value = router.query[key]; | |
const parsedValue = | |
typeof options?.parse === 'function' | |
? options.parse(value as string, options?.defaultValue) | |
: value; | |
return (parsedValue ?? options?.defaultValue) as TDefaultValue; | |
} | |
export function parseNumber(value: string | undefined, defaultValue = 0): number { | |
if (typeof value === 'undefined') { | |
return defaultValue; | |
} | |
// TODO: What about floats, though? :)) | |
const parsedValue = Number.parseInt(value, 10); | |
if (Number.isNaN(parsedValue)) { | |
return defaultValue; | |
} | |
return parsedValue; | |
} | |
export function parseBoolean<TDefaultValue>( | |
value: string | undefined, | |
defaultValue?: TDefaultValue, | |
) { | |
if (typeof value === 'undefined') { | |
return defaultValue; | |
} | |
if (value === 'true') { | |
return true; | |
} | |
if (value === 'false') { | |
return false; | |
} | |
return defaultValue; | |
} | |
export function parseObject<TDefaultValue>( | |
value: string | undefined, | |
defaultValue: TDefaultValue, | |
): TDefaultValue { | |
if (typeof value === 'undefined') { | |
return defaultValue; | |
} | |
try { | |
return JSON.parse(value) as TDefaultValue; | |
} catch { | |
return defaultValue; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useUpdateQueryParam, UseUpdateQueryParamOptions } from './useUpdateQueryParam'; | |
import { useQueryParam, UseQueryParamOptions } from './useQueryParam'; | |
export function useQueryParamState<S>( | |
key: string, | |
options?: UseQueryParamOptions<S> & UseUpdateQueryParamOptions, | |
): [ReturnType<typeof useQueryParam<S>>, ReturnType<typeof useUpdateQueryParam<S>>] { | |
const state = useQueryParam<S>(key, options); | |
const setState = useUpdateQueryParam<S>(key, options); | |
return [state, setState]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ParsedUrlQuery } from 'querystring'; | |
import { useRouter } from 'next/router'; | |
import { useCallback } from 'react'; | |
export type UseUpdateQueryParamOptions = { | |
/** | |
* When true, uses browser history push instead of, the default, replace | |
* Can also be overridden when calling the set method | |
*/ | |
pushHistory?: boolean; | |
}; | |
/** | |
* The hook returns a function that updates a value to the query string using the Next.js router | |
* The value can be any value, and it is serialized to string | |
*/ | |
export function useUpdateQueryParam<T = unknown>( | |
key: string, | |
{ pushHistory = false }: UseUpdateQueryParamOptions = {}, | |
) { | |
const { isReady, pathname, asPath, query, replace: replaceRoute, push: pushRoute } = useRouter(); | |
const updateUrlQueryParam = useCallback( | |
async (nextQuery: ParsedUrlQuery, usePushHistory: boolean) => { | |
if (!isReady) { | |
return false; | |
} | |
// Filter out query values included in the dynamic route | |
const queryWithoutDynamicRouteParams = Object.entries(nextQuery ?? {}).reduce( | |
(acc, [key, value]) => { | |
if (pathname.includes(`[${key}]`)) { | |
return acc; | |
} | |
return { ...acc, [key]: value }; | |
}, | |
{}, | |
); | |
const updateRoute = pushHistory || usePushHistory ? pushRoute : replaceRoute; | |
return updateRoute({ | |
pathname: asPath.split('?')[0], | |
query: queryWithoutDynamicRouteParams, | |
}); | |
}, | |
[asPath, isReady, pathname, pushHistory, pushRoute, replaceRoute], | |
); | |
return useCallback( | |
async (value: T, { usePushHistory = pushHistory }: { usePushHistory?: boolean } = {}) => { | |
if (typeof value === 'undefined') { | |
const queryWithoutKey = { ...query }; | |
delete queryWithoutKey[key]; | |
return updateUrlQueryParam(queryWithoutKey, usePushHistory); | |
} | |
return updateUrlQueryParam( | |
{ | |
...query, | |
[key]: typeof value !== 'string' ? JSON.stringify(value) : value, | |
}, | |
usePushHistory, | |
); | |
}, | |
[pushHistory, query, updateUrlQueryParam, key], | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment