-
Notifications
You must be signed in to change notification settings - Fork 5.1k
/
Copy pathuseLocalStorage.ts
107 lines (89 loc) · 3.06 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
100
101
102
103
104
105
106
107
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useState,
} from "react"
import { useEventCallback } from "./useEventCallback"
import { useEventListener } from "./useEventListener"
// Code source: https://usehooks-ts.com/react-hook/use-local-storage
declare global {
interface WindowEventMap {
"local-storage": CustomEvent
}
}
type SetValue<T> = Dispatch<SetStateAction<T>>
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, SetValue<T>] {
// Get from local storage then
// parse stored json or return initialValue
const readValue = useCallback((): T => {
// Prevent build error "window is undefined" but keeps working
if (typeof window === "undefined") {
return initialValue
}
try {
const item = window.localStorage.getItem(key)
return item ? (parseJSON(item) as T) : initialValue
} catch (error) {
console.warn(`Error reading localStorage key “${key}”:`, error)
return initialValue
}
}, [initialValue, key])
// State to store our value
// Pass initial value to support hydration server-client
const [storedValue, setStoredValue] = useState<T>(initialValue)
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue: SetValue<T> = useEventCallback((value) => {
// Prevent build error "window is undefined" but keeps working
if (typeof window === "undefined") {
console.warn(
`Tried setting localStorage key “${key}” even though environment is not a client`
)
}
try {
// Allow value to be a function so we have the same API as useState
const newValue = value instanceof Function ? value(storedValue) : value
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(newValue))
// Save state
setStoredValue(newValue)
// We dispatch a custom event so every useLocalStorage hook are notified
window.dispatchEvent(new Event("local-storage"))
} catch (error) {
console.warn(`Error setting localStorage key “${key}”:`, error)
}
})
useEffect(() => {
setStoredValue(readValue())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const handleStorageChange = useCallback(
(event: StorageEvent | CustomEvent) => {
if ((event as StorageEvent)?.key && (event as StorageEvent).key !== key) {
return
}
setStoredValue(readValue())
},
[key, readValue]
)
// this only works for other documents, not the current one
useEventListener("storage", handleStorageChange)
// this is a custom event, triggered in writeValueToLocalStorage
// See: useLocalStorage()
useEventListener("local-storage", handleStorageChange)
return [storedValue, setValue]
}
// A wrapper for "JSON.parse()"" to support "undefined" value
function parseJSON<T>(value: string | null): T | undefined {
try {
return value === "undefined" ? undefined : JSON.parse(value ?? "")
} catch {
console.log("parsing error on", { value })
return undefined
}
}