useUrlState.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import { useRouter } from 'next/router'
  2. import { useCallback, useMemo, type Dispatch, type SetStateAction } from 'react'
  3. import useLatest from '@/hooks/misc/useLatest'
  4. export type UrlStateParams = {
  5. [k: string]: string | string[] | undefined
  6. }
  7. /** @deprecated Use useQueryState from nuqs instead for URL state */
  8. export function useUrlState<ValueParams extends UrlStateParams>({
  9. replace = true,
  10. arrayKeys = [],
  11. }: {
  12. /** Whether to use push state routing (working back button), or just replace the current URL
  13. * @default true
  14. */
  15. replace?: boolean
  16. arrayKeys?: string[]
  17. } = {}): [ValueParams, Dispatch<SetStateAction<ValueParams>>] {
  18. const stringifiedArrayKeys = JSON.stringify(arrayKeys)
  19. // eslint-disable-next-line react-hooks/exhaustive-deps
  20. const arrayKeysSet = useMemo(() => new Set(arrayKeys), [stringifiedArrayKeys])
  21. const router = useRouter()
  22. const params: ValueParams = useMemo(() => {
  23. return Object.fromEntries(
  24. Object.entries(router.query).map(([key, value]) => {
  25. if (arrayKeysSet.has(key)) {
  26. return Array.isArray(value) ? [key, value] : [key, [value]]
  27. }
  28. return [key, value]
  29. })
  30. )
  31. }, [arrayKeysSet, router.query])
  32. const paramsRef = useLatest(params)
  33. const setParams: Dispatch<SetStateAction<ValueParams>> = useCallback(
  34. (newParams) => {
  35. const params = paramsRef.current
  36. const nextParams = typeof newParams === 'function' ? newParams(params) : newParams
  37. let newQuery = Object.fromEntries(
  38. Object.entries({ ...params, ...nextParams }).filter(([, value]) => Boolean(value))
  39. )
  40. const replaceOrPush = replace ? router.replace : router.push
  41. replaceOrPush(
  42. {
  43. pathname: router.pathname,
  44. query: newQuery,
  45. },
  46. undefined,
  47. { shallow: true, scroll: false }
  48. )
  49. },
  50. // eslint-disable-next-line react-hooks/exhaustive-deps
  51. [router, replace]
  52. )
  53. return [params, setParams] as const
  54. }