76 lines
2.3 KiB
TypeScript
76 lines
2.3 KiB
TypeScript
|
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<T> {
|
||
|
excludeExternal?: boolean;
|
||
|
defaultValue?: T;
|
||
|
}
|
||
|
|
||
|
export const useLocalStorage = <T extends string = string>(
|
||
|
key: LocalStorageKey,
|
||
|
options: LocalStorageOptions<T> = {}
|
||
|
): [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<T | null>(
|
||
|
(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<string>(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 = <T>(
|
||
|
key: LocalStorageKey,
|
||
|
defaultValue: T,
|
||
|
options?: LocalStorageOptions<T>
|
||
|
): [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;
|