Created
May 13, 2022 13:20
-
-
Save SirMoustache/0e0b0dae7cf1fd96e2d419ecfc5d92c8 to your computer and use it in GitHub Desktop.
useLocalStorageValue react hook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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