-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
/
useLocalStorage.ts
99 lines (86 loc) Β· 2.98 KB
/
useLocalStorage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { Dispatch, SetStateAction, useCallback, useState, useRef, useLayoutEffect } from 'react';
import { isBrowser, noop } from './misc/util';
type parserOptions<T> =
| {
raw: true;
}
| {
raw: false;
serializer: (value: T) => string;
deserializer: (value: string) => T;
};
const useLocalStorage = <T>(
key: string,
initialValue?: T,
options?: parserOptions<T>
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {
if (!isBrowser) {
return [initialValue as T, noop, noop];
}
if (!key) {
throw new Error('useLocalStorage key may not be falsy');
}
const deserializer = options
? options.raw
? (value) => value
: options.deserializer
: JSON.parse;
// eslint-disable-next-line react-hooks/rules-of-hooks
const initializer = useRef((key: string) => {
try {
const serializer = options ? (options.raw ? String : options.serializer) : JSON.stringify;
const localStorageValue = localStorage.getItem(key);
if (localStorageValue !== null) {
return deserializer(localStorageValue);
} else {
initialValue && localStorage.setItem(key, serializer(initialValue));
return initialValue;
}
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. JSON.parse and JSON.stringify
// can throw, too.
return initialValue;
}
});
// eslint-disable-next-line react-hooks/rules-of-hooks
const [state, setState] = useState<T | undefined>(() => initializer.current(key));
// eslint-disable-next-line react-hooks/rules-of-hooks
useLayoutEffect(() => setState(initializer.current(key)), [key]);
// eslint-disable-next-line react-hooks/rules-of-hooks
const set: Dispatch<SetStateAction<T | undefined>> = useCallback(
(valOrFunc) => {
try {
const newState =
typeof valOrFunc === 'function' ? (valOrFunc as Function)(state) : valOrFunc;
if (typeof newState === 'undefined') return;
let value: string;
if (options)
if (options.raw)
if (typeof newState === 'string') value = newState;
else value = JSON.stringify(newState);
else if (options.serializer) value = options.serializer(newState);
else value = JSON.stringify(newState);
else value = JSON.stringify(newState);
localStorage.setItem(key, value);
setState(deserializer(value));
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw. Also JSON.stringify can throw.
}
},
[key, setState]
);
// eslint-disable-next-line react-hooks/rules-of-hooks
const remove = useCallback(() => {
try {
localStorage.removeItem(key);
setState(undefined);
} catch {
// If user is in private mode or has storage restriction
// localStorage can throw.
}
}, [key, setState]);
return [state, set, remove];
};
export default useLocalStorage;