stateful-params
Version:
A lightweight and intuitive React hook for managing URL query parameters with ease. Stateful-params enables seamless CRUD operations on URL query parameters while keeping your UI in sync with the URL state. Perfect for building dynamic, stateful, and shar
120 lines (100 loc) • 3.72 kB
text/typescript
import { useState, useEffect } from "react"
/**
* Hook for managing URL query parameters
* @template TParams - The shape of the query parameters.
* @param {object} [options] - Configuration options.
* @param {boolean} [options.deleteFalseValues=false] - Whether to delete the parameter if the parameter value is a false value.
* @param {'replace' | 'push'} [options.method='push'] - Method to use for history updates.
* @returns {object} - An object containing methods to set, append, get, delete query parameters and searchParams.
*/
export const useStatefulParams = <TParams extends Record<string, any>>(
options: {
method?: "replace" | "push"
deleteFalseValues?: boolean
} = {}
) => {
const { method = "push", deleteFalseValues = true } = options
const [searchParams, setSearchParams] = useState<URLSearchParams>(() => {
return new URLSearchParams(window.location.search)
})
useEffect(() => {
const handlePopState = () => {
const newParams = new URLSearchParams(window.location.search)
setSearchParams(newParams)
}
window.addEventListener("popstate", handlePopState)
return () => window.removeEventListener("popstate", handlePopState)
}, [])
const updateUrl = (newParams: URLSearchParams) => {
const newParamsString = newParams.toString()
const currentParamsString = searchParams.toString()
if (newParamsString !== currentParamsString) {
const newUrl = new URL(window.location.href)
newUrl.search = newParamsString
if (method === "replace") {
window.history.replaceState(null, "", newUrl)
} else {
window.history.pushState(null, "", newUrl)
}
setSearchParams(newParams)
}
}
const set = (params: Partial<TParams>) => {
const newSearchParams = new URLSearchParams(searchParams.toString())
for (const [key, value] of Object.entries(params)) {
if (
value === null ||
value === undefined ||
(deleteFalseValues && !value)
) {
newSearchParams.delete(key)
} else if (Array.isArray(value)) {
newSearchParams.delete(key)
value.forEach(val => newSearchParams.append(key, String(val)))
} else {
newSearchParams.set(key, String(value))
}
}
updateUrl(newSearchParams)
return Object.fromEntries(newSearchParams.entries())
}
const append = (params: Partial<TParams>) => {
const newSearchParams = new URLSearchParams(searchParams.toString())
for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
value.forEach(val => newSearchParams.append(key, String(val)))
} else {
newSearchParams.append(key, String(value))
}
}
updateUrl(newSearchParams)
return Object.fromEntries(newSearchParams.entries())
}
const deleteParam = (keys: keyof TParams | (keyof TParams)[]) => {
const newSearchParams = new URLSearchParams(searchParams.toString())
if (Array.isArray(keys)) {
keys.forEach(key => newSearchParams.delete(key as string))
} else {
newSearchParams.delete(keys as string)
}
updateUrl(newSearchParams)
return Object.fromEntries(newSearchParams.entries())
}
const clear = () => {
const newSearchParams = new URLSearchParams()
updateUrl(newSearchParams)
return Object.fromEntries(newSearchParams.entries())
}
const get = <K extends keyof TParams>(key: K): TParams[K] | null => {
return searchParams.get(key as string) as TParams[K] | null
}
const searchParamsObj = Object.fromEntries(searchParams.entries()) as TParams
return {
set,
append,
deleteParam,
clear,
get,
searchParams: searchParamsObj
}
}