Skip to content

Instantly share code, notes, and snippets.

@SirMoustache
Created May 13, 2022 13:20
Show Gist options
  • Select an option

  • Save SirMoustache/0e0b0dae7cf1fd96e2d419ecfc5d92c8 to your computer and use it in GitHub Desktop.

Select an option

Save SirMoustache/0e0b0dae7cf1fd96e2d419ecfc5d92c8 to your computer and use it in GitHub Desktop.
useLocalStorageValue react hook
import { Dispatch, SetStateAction, useEffect, useState, useCallback } from 'react';
type SetValue<T> = Dispatch<SetStateAction<T>>;
type CleanValue = () => void;
type CustomEventDetail = { key: string };
const CUSTOM_STORAGE_EVENT = 'webshop:storage';
declare global {
interface WindowEventMap {
[CUSTOM_STORAGE_EVENT]: CustomEvent<CustomEventDetail>;
}
}
export const isSSR = typeof window === 'undefined';
export const isFunction = (value: unknown): value is Function => typeof value === 'function';
const readLocalStorage = <T>(
key: string,
defaultValue: T | undefined = undefined
): T | undefined => {
if (isSSR) {
return defaultValue;
}
try {
const item = window.localStorage.getItem(key);
const nullableStringValues = ['undefined'];
const isNullable = nullableStringValues.some((nullable) => nullable === item);
return item && !isNullable ? (JSON.parse(item) as T) : defaultValue;
} catch (error) {
console.warn(`Error reading storage key "${key}":`, error);
return defaultValue;
}
};
const writeLocalStorage = <T>(key: string, value: T): void => {
if (isSSR) {
console.warn(`Tried setting storage key '${key}' even though environment is not a client`);
}
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.warn(`Error setting localStorage key '${key}':`, error);
}
};
const cleanLocalStorage = (key: string): void => {
if (isSSR) {
console.warn(`Tried clean storage key '${key}' even though environment is not a client`);
}
try {
localStorage.removeItem(key);
} catch (error) {
console.warn(`Error cleaning localStorage key '${key}':`, error);
}
};
const dispatchCustomStorageEvent = (key: string) => {
window.dispatchEvent(
new CustomEvent<CustomEventDetail>(CUSTOM_STORAGE_EVENT, {
detail: {
key,
},
})
);
};
export const useLocalStorageValue = <T>(
key: string,
initialValue?: T
): [T | undefined, SetValue<T | undefined>, CleanValue] => {
const readValue = useCallback((): T | undefined => {
return readLocalStorage<T>(key, initialValue);
}, [initialValue, key]);
const [storedValue, setStoredValue] = useState<T | undefined>(readValue);
const setValue: SetValue<T | undefined> = useCallback(
(value) => {
const currentValue = readLocalStorage<T>(key);
const newValue = isFunction(value) ? value(currentValue) : value;
writeLocalStorage(key, newValue);
dispatchCustomStorageEvent(key);
},
[key]
);
const clean = useCallback(() => {
cleanLocalStorage(key);
dispatchCustomStorageEvent(key);
}, [key]);
// Update value on key update
useEffect(() => {
const newVal = readLocalStorage<T>(key);
setValue(newVal);
}, [key, setValue]);
// Watch for localStorage event changes
useEffect(() => {
const handleStorageChange = (event: StorageEvent) => {
const eventKey = event.key;
if (eventKey !== key) return;
const newVal = readLocalStorage<T>(key);
setStoredValue(newVal);
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [key]);
// Watch for localStorage custom event changes
useEffect(() => {
const handleCustomeStorageChange = (event: CustomEvent<CustomEventDetail>) => {
const eventKey = event.detail.key;
if (eventKey !== key) return;
const newVal = readLocalStorage<T>(key);
setStoredValue(newVal);
};
window.addEventListener(CUSTOM_STORAGE_EVENT, handleCustomeStorageChange);
return () => {
window.removeEventListener(CUSTOM_STORAGE_EVENT, handleCustomeStorageChange);
};
}, [key]);
return [storedValue, setValue, clean];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment