import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { getLocalStorage, subscribeToLocalStorage, setLocalStorage } from './localStorage'; export const AVAILABLE_KEYS = [ "stats", "name", "password" ] as const; export type LocalStorageKey = typeof AVAILABLE_KEYS[number]; interface LocalStorageOptions { excludeExternal?: boolean; defaultValue?: T; } export const useLocalStorage = ( key: LocalStorageKey, options: LocalStorageOptions = {} ): [T | null, (value: T) => void] => { // We track the localStorage value as state so that we can re-render when it // is updated (see the subscription useEffect for how that works) const [valueState, setValueState] = useState( (getLocalStorage(key) as T) || options.defaultValue || null ); // This ref is needed because we don't want to ever redefine the setValue // function but we want to be able to use whatever the latest key is when we // do setValue const keyRef = useRef(key); useEffect(() => { keyRef.current = key; }, [key]); // We want this set function to never be redefined, so we use useCallback with // an empty array and only reference the key by ref const setValue = useCallback((value: T) => { setLocalStorage(keyRef.current, value); }, []); // subscribe to localstorage changes and set the `currentValue` state on // change. useEffect(() => { // make sure to return. The subscribe function returns an unsubscribe // function return subscribeToLocalStorage(key, setValueState as (val: string) => void, options); }, [key, options]); return [valueState, setValue]; }; export const useJSONLocalStorage = ( key: LocalStorageKey, defaultValue: T, options?: LocalStorageOptions ): [T, (newValue: T) => void] => { const [stringValue, setStringValue] = useLocalStorage(key, { ...options, defaultValue: defaultValue && JSON.stringify(defaultValue) }); const value = useMemo(() => { try { return JSON.parse(stringValue as string); } catch { return defaultValue; } }, [stringValue, defaultValue]); const setValue = (newValue: T) => setStringValue(JSON.stringify(newValue)); return [value, setValue]; }; export default useLocalStorage;