useQueryParamsStore
is a custom React hook that stores and retrieves data within URL query parameters.
This functionality not only maintains data across page refreshes but also enables users to share a state with others preventing sensitive information from being exposed in plain text within the browser's URL bar.
// queryparams.store.ts
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
export function useQueryParamsStore<T extends Record<string, unknown>>(paramKey: string) {
const [data, _setData] = useState<T>({} as T);
const [searchParams, setSearchParams] = useSearchParams();
useEffect(() => {
fetchDataFromQueryParam();
}, [paramKey, searchParams]);
const setData = (newData: Partial<T>) =>
setSearchParams((prevParams) => {
const newParams = new URLSearchParams(prevParams);
const updatedData = { ...data, ...newData };
if (hasData(updatedData))
newParams.set(paramKey, objectToSafeBase64<Partial<T>>(updatedData));
else newParams.delete(paramKey);
return newParams;
});
const fetchDataFromQueryParam = () => {
const paramValue = searchParams.get(paramKey);
if (paramValue) _setData(safeBase64ToObject<T>(paramValue));
else resetData();
};
const resetData = () => _setData({} as T);
return [data, setData] as [Partial<T>, (newData: Partial<T>) => void];
}
const objectToSafeBase64 = <T>(obj: T) => generateSafeBase64(JSON.stringify(obj));
const generateSafeBase64 = (data: string) =>
btoa(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
const safeBase64ToObject = <T>(safeBase64: string): T => JSON.parse(decodeSafeBase64(safeBase64));
const decodeSafeBase64 = (safeBase64: string) =>
atob(safeBase64.padEnd((4 - (safeBase64.length % 4)) % 4, '='));
const hasData = <T extends Record<string, unknown>>(data: T) =>
Object.values(data).some((d) => (d instanceof Array ? !!d.length : !!d));
// search.component.tsx
import { useQueryParamsStore } from './queryparams.store';
import React from 'react';
type SearchState = {
query: string;
cbs: string[];
};
const options = ['a', 'b', 'c']
export const Search = () => {
const [data, setData] = useQueryParamsStore<SearchState>('searchState');
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target) setData({ query: e.target.value });
};
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target) {
const option = e.target.value as SearchEntity;
const { cbs = [] } = data;
if (isCBChecked(option)) setData({ cbs: cbs.filter((e) => e !== option) });
else setData({ cbs: [...cbs, option] });
}
};
const isCBChecked = (option) => data.cbs?.includes(option);
return (
<>
<input
value={data.query || ''}
onChange={handleInputChange}
/>
{options.map((option, index) => (
<label key={index}>
<input
type="checkbox"
value={option}
checked={isCBChecked(option)}
onChange={handleCheckboxChange}
/>
{option}
</label>
))}
</>
);
};