forked from mantinedev/mantine
/
create-storage.ts
118 lines (98 loc) · 3.35 KB
/
create-storage.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
108
109
110
111
112
113
114
115
116
117
118
import { useState, useCallback, useEffect } from 'react';
import { useWindowEvent } from '../use-window-event/use-window-event';
export type StorageType = 'localStorage' | 'sessionStorage';
export interface IStorageProperties<T> {
/** Storage key */
key: string;
/** Default value that will be set if value is not found in storage */
defaultValue?: T;
/** If set to true, value will be update is useEffect after mount */
getInitialValueInEffect?: boolean;
/** Function to serialize value into string to be save in storage */
serialize?(value: T): string;
/** Function to deserialize string value from storage to value */
deserialize?(value: string): T;
}
function serializeJSON<T>(value: T, hookName: string) {
try {
return JSON.stringify(value);
} catch (error) {
throw new Error(`@mantine/hooks ${hookName}: Failed to serialize the value`);
}
}
function deserializeJSON(value: string) {
try {
return JSON.parse(value);
} catch {
return value;
}
}
export function createStorage<T>(type: StorageType, hookName: string) {
const eventName = type === 'localStorage' ? 'mantine-local-storage' : 'mantine-session-storage';
return function useStorage({
key,
defaultValue = undefined,
getInitialValueInEffect = true,
deserialize = deserializeJSON,
serialize = (value: T) => serializeJSON(value, hookName),
}: IStorageProperties<T>) {
const readStorageValue = useCallback(
(skipStorage?: boolean): T => {
if (typeof window === 'undefined' || !(type in window) || skipStorage) {
return (defaultValue ?? '') as T;
}
const storageValue = window[type].getItem(key);
return storageValue !== null ? deserialize(storageValue) : ((defaultValue ?? '') as T);
},
[key, defaultValue]
);
const [value, setValue] = useState<T>(readStorageValue(getInitialValueInEffect));
const setStorageValue = useCallback(
(val: T | ((prevState: T) => T)) => {
if (val instanceof Function) {
setValue((current) => {
const result = val(current);
window[type].setItem(key, serialize(result));
window.dispatchEvent(
new CustomEvent(eventName, { detail: { key, value: val(current) } })
);
return result;
});
} else {
window[type].setItem(key, serialize(val));
window.dispatchEvent(new CustomEvent(eventName, { detail: { key, value: val } }));
setValue(val);
}
},
[key]
);
const removeStorageValue = useCallback(() => {
window[type].removeItem(key);
}, []);
useWindowEvent('storage', (event) => {
if (event.storageArea === window[type] && event.key === key) {
setValue(deserialize(event.newValue ?? undefined));
}
});
useWindowEvent(eventName, (event) => {
if (event.detail.key === key) {
setValue(event.detail.value);
}
});
useEffect(() => {
if (defaultValue !== undefined && value === undefined) {
setStorageValue(defaultValue);
}
}, [defaultValue, value, setStorageValue]);
useEffect(() => {
if (getInitialValueInEffect) {
setValue(readStorageValue());
}
}, []);
return [
value === undefined ? defaultValue : value,
setStorageValue,
removeStorageValue,
] as const;
};
}