12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- import { Dispatch, SetStateAction, useEffect, useState } from 'react';
- type SetValue<T> = Dispatch<SetStateAction<T>>;
- function useLocalStorage<T>(key: string, initialValue: T): [T, SetValue<T>] {
- // Get from local storage then
- // parse stored json or return initialValue
- const readValue = (): T => {
- // Prevent build error "window is undefined" but keep keep working
- if (typeof window === 'undefined') {
- return initialValue;
- }
- try {
- const item = window.localStorage.getItem(key);
- return item ? (parseJSON(item) as T) : initialValue;
- } catch (error) {
- console.warn(`Error reading localStorage key “${key}”:`, error);
- return initialValue;
- }
- };
- // State to store our value
- // Pass initial state function to useState so logic is only executed once
- const [storedValue, setStoredValue] = useState<T>(readValue);
- // Return a wrapped version of useState's setter function that ...
- // ... persists the new value to localStorage.
- const setValue: SetValue<T> = (value) => {
- // Prevent build error "window is undefined" but keeps working
- if (typeof window === 'undefined') {
- console.warn(`Tried setting localStorage key “${key}” even though environment is not a client`);
- }
- try {
- // Allow value to be a function so we have the same API as useState
- const newValue = value instanceof Function ? value(storedValue) : value;
- // Save to local storage
- window.localStorage.setItem(key, JSON.stringify(newValue));
- // Save state
- setStoredValue(newValue);
- // We dispatch a custom event so every useLocalStorage hook are notified
- window.dispatchEvent(new Event('local-storage'));
- } catch (error) {
- console.warn(`Error setting localStorage key “${key}”:`, error);
- }
- };
- useEffect(() => {
- setStoredValue(readValue());
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- useEffect(() => {
- const handleStorageChange = () => {
- setStoredValue(readValue());
- };
- // this only works for other documents, not the current one
- window.addEventListener('storage', handleStorageChange);
- // this is a custom event, triggered in writeValueToLocalStorage
- window.addEventListener('local-storage', handleStorageChange);
- return () => {
- window.removeEventListener('storage', handleStorageChange);
- window.removeEventListener('local-storage', handleStorageChange);
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- return [storedValue, setValue];
- }
- export default useLocalStorage;
- // A wrapper for "JSON.parse()"" to support "undefined" value
- function parseJSON<T>(value: string | null): T | undefined {
- try {
- return value === 'undefined' ? undefined : JSON.parse(value ?? '');
- } catch (error) {
- console.log('parsing error on', { value });
- return undefined;
- }
- }
|