potty-chart/src/hooks/useLocalStorage.ts

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;