Last active
May 26, 2023 12:09
-
-
Save tekno0ryder/469ce409012a20a32f3ed45d152a4328 to your computer and use it in GitHub Desktop.
Custom hook to store state in query params
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 { createContext, useContext, useEffect, useRef, useState } from "react"; | |
import { useSearchParams } from "react-router-dom"; | |
const ISO_8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; | |
const isFalsy = (value: any) => | |
!value || | |
JSON.stringify(value) === "{}" || | |
(Array.isArray(value) && value.length === 0); | |
const parseDate = (k: string, v: any) => { | |
if (ISO_8601.test(v)) { | |
return new Date(v); | |
} | |
return v; | |
}; | |
export const SearchParamsContext = createContext<URLSearchParams | null>(null); | |
export const SearchParamsProvider = ({ | |
children, | |
}: { | |
children: React.ReactNode | React.ReactNode[]; | |
}) => { | |
const [searchParams] = useSearchParams(); | |
return ( | |
<SearchParamsContext.Provider value={searchParams}> | |
{children} | |
</SearchParamsContext.Provider> | |
); | |
}; | |
export const useQueryParam = <T,>(key: string, initialValue: T) => { | |
const searchParams = useContext(SearchParamsContext); | |
const [, setSearchParams] = useSearchParams(); | |
if (!searchParams) { | |
throw new Error("SearchParams provider is required"); | |
} | |
const queryValue = searchParams.get(key); | |
// Memoize prev (queryValue) to detect if it has changed externally and not from this hook to sync it back. | |
const queryValueRef = useRef<string | null>(queryValue); | |
const [value, setValue] = useState(() => | |
queryValue !== null | |
? (JSON.parse(queryValue, parseDate) as T) | |
: initialValue | |
); | |
// Sync query back to state if change is detected | |
useEffect(() => { | |
if (queryValue !== null && queryValue !== queryValueRef.current) { | |
setValue(JSON.parse(queryValue, parseDate) as T); | |
queryValueRef.current = queryValue; | |
} | |
}, [queryValue]); | |
const set = (newValue: T | ((v: T) => T)) => { | |
const invokedValue = | |
newValue instanceof Function ? newValue(value) : newValue; | |
if (isFalsy(invokedValue)) { | |
setValue(initialValue); | |
searchParams.delete(key); | |
queryValueRef.current = null; | |
} else { | |
setValue(invokedValue); | |
const invokedStringified = JSON.stringify(invokedValue); | |
searchParams.set(key, invokedStringified); | |
queryValueRef.current = invokedStringified; | |
} | |
setSearchParams(searchParams, { replace: true }); | |
}; | |
return [value, set] as const; | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment