Skip to content

Instantly share code, notes, and snippets.

@tekno0ryder
Last active May 26, 2023 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tekno0ryder/469ce409012a20a32f3ed45d152a4328 to your computer and use it in GitHub Desktop.
Save tekno0ryder/469ce409012a20a32f3ed45d152a4328 to your computer and use it in GitHub Desktop.
Custom hook to store state in query params
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