use-local-storage
Version:
A flexible React Hook for using Local Storage.
118 lines (100 loc) • 3.01 kB
text/typescript
import { useEffect, useMemo, useRef, useState } from "react";
type Serializer<T> = (object: T | undefined) => string;
type Parser<T> = (val: string) => T | undefined;
type Setter<T> = React.Dispatch<React.SetStateAction<T | undefined>>;
type Options<T> = Partial<{
serializer: Serializer<T>;
parser: Parser<T>;
logger: (error: any) => void;
syncData: boolean;
}>;
function useLocalStorage<T>(
key: string,
defaultValue: T,
options?: Options<T>
): [T, Setter<T>];
function useLocalStorage<T>(
key: string,
defaultValue?: T,
options?: Options<T>
) {
const opts = useMemo(() => {
return {
serializer: JSON.stringify,
parser: JSON.parse,
logger: console.log,
syncData: true,
...options,
};
}, [options]);
const { serializer, parser, logger, syncData } = opts;
const rawValueRef = useRef<string | null>(null);
const [value, setValue] = useState(() => {
if (typeof window === "undefined") return defaultValue;
try {
rawValueRef.current = window.localStorage.getItem(key);
const res: T = rawValueRef.current
? parser(rawValueRef.current)
: defaultValue;
return res;
} catch (e) {
logger(e);
return defaultValue;
}
});
useEffect(() => {
if (typeof window === "undefined") return;
const updateLocalStorage = () => {
// Browser ONLY dispatch storage events to other tabs, NOT current tab.
// We need to manually dispatch storage event for current tab
if (value !== undefined) {
const newValue = serializer(value);
const oldValue = rawValueRef.current;
rawValueRef.current = newValue;
window.localStorage.setItem(key, newValue);
window.dispatchEvent(
new StorageEvent("storage", {
storageArea: window.localStorage,
url: window.location.href,
key,
newValue,
oldValue,
})
);
} else {
window.localStorage.removeItem(key);
window.dispatchEvent(
new StorageEvent("storage", {
storageArea: window.localStorage,
url: window.location.href,
key,
})
);
}
};
try {
updateLocalStorage();
} catch (e) {
logger(e);
}
}, [value]);
useEffect(() => {
if (!syncData) return;
const handleStorageChange = (e: StorageEvent) => {
if (e.key !== key || e.storageArea !== window.localStorage) return;
try {
if (e.newValue !== rawValueRef.current) {
rawValueRef.current = e.newValue;
setValue(e.newValue ? parser(e.newValue) : undefined);
}
} catch (e) {
logger(e);
}
};
if (typeof window === "undefined") return;
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [key, syncData]);
return [value, setValue];
}
export default useLocalStorage;