Skip to content

Instantly share code, notes, and snippets.

@peplocanto
Last active November 21, 2023 16:18
Show Gist options
  • Save peplocanto/d69479f1686e53f9feb45672a57af6f4 to your computer and use it in GitHub Desktop.
Save peplocanto/d69479f1686e53f9feb45672a57af6f4 to your computer and use it in GitHub Desktop.
react custom hook implementing a queryparams store

Queryparams Store Hook

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.

Implementation

// 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));

Usage

// 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>
      ))}
    </>
  );
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment