useLocalstorage.ts 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { Dispatch, SetStateAction, useEffect, useState } from 'react';
  2. type SetValue<T> = Dispatch<SetStateAction<T>>;
  3. function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {
  4. // Get from local storage then
  5. // parse stored json or return initialValue
  6. const readValue = (): T => {
  7. // Prevent build error "window is undefined" but keep keep working
  8. if (typeof window === 'undefined') {
  9. return initialValue;
  10. }
  11. try {
  12. const item = window.localStorage.getItem(key);
  13. return item ? (parseJSON(item) as T) : initialValue;
  14. } catch (error) {
  15. console.warn(`Error reading localStorage key “${key}”:`, error);
  16. return initialValue;
  17. }
  18. };
  19. // State to store our value
  20. // Pass initial state function to useState so logic is only executed once
  21. const [storedValue, setStoredValue] = useState<T>(readValue);
  22. // Return a wrapped version of useState's setter function that ...
  23. // ... persists the new value to localStorage.
  24. const setValue: SetValue<T> = (value) => {
  25. // Prevent build error "window is undefined" but keeps working
  26. if (typeof window === 'undefined') {
  27. console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`);
  28. }
  29. try {
  30. // Allow value to be a function so we have the same API as useState
  31. const newValue = value instanceof Function ? value(storedValue) : value;
  32. // Save to local storage
  33. window.localStorage.setItem(key, JSON.stringify(newValue));
  34. // Save state
  35. setStoredValue(newValue);
  36. // We dispatch a custom event so every useLocalStorage hook are notified
  37. window.dispatchEvent(new Event('local-storage'));
  38. } catch (error) {
  39. console.warn(`Error setting localStorage key “${key}”:`, error);
  40. }
  41. };
  42. useEffect(() => {
  43. setStoredValue(readValue());
  44. // eslint-disable-next-line react-hooks/exhaustive-deps
  45. }, []);
  46. useEffect(() => {
  47. const handleStorageChange = () => {
  48. setStoredValue(readValue());
  49. };
  50. // this only works for other documents, not the current one
  51. window.addEventListener('storage', handleStorageChange);
  52. // this is a custom event, triggered in writeValueToLocalStorage
  53. window.addEventListener('local-storage', handleStorageChange);
  54. return () => {
  55. window.removeEventListener('storage', handleStorageChange);
  56. window.removeEventListener('local-storage', handleStorageChange);
  57. };
  58. // eslint-disable-next-line react-hooks/exhaustive-deps
  59. }, []);
  60. return [storedValue, setValue];
  61. }
  62. export default useLocalStorage;
  63. // A wrapper for "JSON.parse()"" to support "undefined" value
  64. function parseJSON<T>(value: string | null): T | undefined {
  65. try {
  66. return value === 'undefined' ? undefined : JSON.parse(value ?? '');
  67. } catch (error) {
  68. console.log('parsing error on', { value });
  69. return undefined;
  70. }
  71. }