@@ -2,6 +2,8 @@ import { atom } from 'jotai'
2
2
import type { WritableAtom } from 'jotai'
3
3
import { RESET } from './constants'
4
4
5
+ export const NO_STORAGE_VALUE = Symbol ( )
6
+
5
7
type Unsubscribe = ( ) => void
6
8
7
9
type SetStateActionWithReset < Value > =
@@ -10,15 +12,15 @@ type SetStateActionWithReset<Value> =
10
12
| ( ( prev : Value ) => Value | typeof RESET )
11
13
12
14
export interface AsyncStorage < Value > {
13
- getItem : ( key : string ) => Promise < Value >
15
+ getItem : ( key : string ) => Promise < Value | typeof NO_STORAGE_VALUE >
14
16
setItem : ( key : string , newValue : Value ) => Promise < void >
15
17
removeItem : ( key : string ) => Promise < void >
16
18
delayInit ?: boolean
17
19
subscribe ?: ( key : string , callback : ( value : Value ) => void ) => Unsubscribe
18
20
}
19
21
20
22
export interface SyncStorage < Value > {
21
- getItem : ( key : string ) => Value
23
+ getItem : ( key : string ) => Value | typeof NO_STORAGE_VALUE
22
24
setItem : ( key : string , newValue : Value ) => void
23
25
removeItem : ( key : string ) => void
24
26
delayInit ?: boolean
@@ -46,45 +48,56 @@ export function createJSONStorage<Value>(
46
48
) : SyncStorage < Value >
47
49
48
50
export function createJSONStorage < Value > (
49
- getStringStorage : ( ) => AsyncStringStorage | SyncStringStorage
51
+ getStringStorage : ( ) => AsyncStringStorage | SyncStringStorage | undefined
50
52
) : AsyncStorage < Value > | SyncStorage < Value > {
51
53
let lastStr : string | undefined
52
54
let lastValue : any
53
- return {
55
+ const storage : AsyncStorage < Value > | SyncStorage < Value > = {
54
56
getItem : ( key ) => {
55
57
const parse = ( str : string | null ) => {
56
58
str = str || ''
57
59
if ( lastStr !== str ) {
58
- lastValue = JSON . parse ( str )
60
+ try {
61
+ lastValue = JSON . parse ( str )
62
+ } catch {
63
+ return NO_STORAGE_VALUE
64
+ }
59
65
lastStr = str
60
66
}
61
67
return lastValue
62
68
}
63
- const str = getStringStorage ( ) . getItem ( key )
69
+ const str = getStringStorage ( ) ? .getItem ( key ) ?? null
64
70
if ( str instanceof Promise ) {
65
71
return str . then ( parse )
66
72
}
67
73
return parse ( str )
68
74
} ,
69
75
setItem : ( key , newValue ) =>
70
- getStringStorage ( ) . setItem ( key , JSON . stringify ( newValue ) ) ,
71
- removeItem : ( key ) => getStringStorage ( ) . removeItem ( key ) ,
76
+ getStringStorage ( ) ? .setItem ( key , JSON . stringify ( newValue ) ) ,
77
+ removeItem : ( key ) => getStringStorage ( ) ? .removeItem ( key ) ,
72
78
}
73
- }
74
-
75
- const defaultStorage = createJSONStorage ( ( ) => localStorage )
76
- defaultStorage . subscribe = ( key , callback ) => {
77
- const storageEventCallback = ( e : StorageEvent ) => {
78
- if ( e . key === key && e . newValue ) {
79
- callback ( JSON . parse ( e . newValue ) )
79
+ if ( typeof window !== 'undefined' ) {
80
+ storage . subscribe = ( key , callback ) => {
81
+ const storageEventCallback = ( e : StorageEvent ) => {
82
+ if ( e . key === key && e . newValue ) {
83
+ callback ( JSON . parse ( e . newValue ) )
84
+ }
85
+ }
86
+ window . addEventListener ( 'storage' , storageEventCallback )
87
+ return ( ) => {
88
+ window . removeEventListener ( 'storage' , storageEventCallback )
89
+ }
80
90
}
81
91
}
82
- window . addEventListener ( 'storage' , storageEventCallback )
83
- return ( ) => {
84
- window . removeEventListener ( 'storage' , storageEventCallback )
85
- }
92
+ return storage
86
93
}
87
94
95
+ const defaultStorage = createJSONStorage ( ( ) =>
96
+ typeof window !== 'undefined'
97
+ ? window . localStorage
98
+ : ( undefined as unknown as Storage )
99
+ )
100
+
88
101
export function atomWithStorage < Value > (
89
102
key : string ,
90
103
initialValue : Value ,
@@ -116,15 +129,11 @@ export function atomWithStorage<Value>(
116
129
| AsyncStorage < Value > = defaultStorage as SyncStorage < Value >
117
130
) {
118
131
const getInitialValue = ( ) => {
119
- try {
120
- const value = storage . getItem ( key )
121
- if ( value instanceof Promise ) {
122
- return value . catch ( ( ) => initialValue )
123
- }
124
- return value
125
- } catch {
126
- return initialValue
132
+ const value = storage . getItem ( key )
133
+ if ( value instanceof Promise ) {
134
+ return value . then ( ( v ) => ( v === NO_STORAGE_VALUE ? initialValue : v ) )
127
135
}
136
+ return value === NO_STORAGE_VALUE ? initialValue : value
128
137
}
129
138
130
139
const baseAtom = atom ( storage . delayInit ? initialValue : getInitialValue ( ) )
@@ -175,14 +184,22 @@ export function atomWithHash<Value>(
175
184
initialValue : Value ,
176
185
options ?: {
177
186
serialize ?: ( val : Value ) => string
178
- deserialize ?: ( str : string ) => Value
187
+ deserialize ?: ( str : string | null ) => Value | typeof NO_STORAGE_VALUE
179
188
delayInit ?: boolean
180
189
replaceState ?: boolean
181
190
subscribe ?: ( callback : ( ) => void ) => ( ) => void
182
191
}
183
192
) : WritableAtom < Value , SetStateActionWithReset < Value > > {
184
193
const serialize = options ?. serialize || JSON . stringify
185
- const deserialize = options ?. deserialize || JSON . parse
194
+ const deserialize =
195
+ options ?. deserialize ||
196
+ ( ( str ) => {
197
+ try {
198
+ return JSON . parse ( str || '' )
199
+ } catch {
200
+ return NO_STORAGE_VALUE
201
+ }
202
+ } )
186
203
const subscribe =
187
204
options ?. subscribe ||
188
205
( ( callback ) => {
@@ -195,9 +212,6 @@ export function atomWithHash<Value>(
195
212
getItem : ( key ) => {
196
213
const searchParams = new URLSearchParams ( location . hash . slice ( 1 ) )
197
214
const storedValue = searchParams . get ( key )
198
- if ( storedValue === null ) {
199
- throw new Error ( 'no value stored' )
200
- }
201
215
return deserialize ( storedValue )
202
216
} ,
203
217
setItem : ( key , newValue ) => {
0 commit comments