Skip to content

Commit caea8dd

Browse files
committed
Removes store caching during SSR
1 parent 5046b27 commit caea8dd

5 files changed

+197
-46
lines changed

index.ts

+50-44
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,20 @@ export function writable<StoreType, SerializerType = StoreType>(key: string, ini
4848
export function persisted<StoreType, SerializerType = StoreType>(key: string, initialValue: StoreType, options?: Options<StoreType, SerializerType>): Persisted<StoreType> {
4949
if (options?.onError) console.warn("onError has been deprecated. Please use onWriteError instead")
5050

51-
const serializer = options?.serializer ?? JSON
5251
const storageType = options?.storage ?? 'local'
52+
const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined'
53+
54+
if (browser && stores[storageType][key]) {
55+
return stores[storageType][key]
56+
}
57+
58+
const serializer = options?.serializer ?? JSON
5359
const syncTabs = options?.syncTabs ?? true
5460
const onWriteError = options?.onWriteError ?? options?.onError ?? ((e) => console.error(`Error when writing value from persisted store "${key}" to ${storageType}`, e))
5561
const onParseError = options?.onParseError ?? ((newVal, e) => console.error(`Error when parsing ${newVal ? '"' + newVal + '"' : "value"} from persisted store "${key}"`, e))
56-
5762
const beforeRead = options?.beforeRead ?? ((val) => val as unknown as StoreType)
5863
const beforeWrite = options?.beforeWrite ?? ((val) => val as unknown as SerializerType)
5964

60-
const browser = typeof (window) !== 'undefined' && typeof (document) !== 'undefined'
6165
const storage = browser ? getStorage(storageType) : null
6266

6367
function updateStorage(key: string, value: StoreType) {
@@ -88,52 +92,54 @@ export function persisted<StoreType, SerializerType = StoreType>(key: string, in
8892
return newVal
8993
}
9094

91-
if (!stores[storageType][key]) {
92-
const initial = maybeLoadInitial()
93-
const store = internal(initial, (set) => {
94-
if (browser && storageType == 'local' && syncTabs) {
95-
const handleStorage = (event: StorageEvent) => {
96-
if (event.key === key && event.newValue) {
97-
let newVal: any
98-
try {
99-
newVal = serializer.parse(event.newValue)
100-
} catch (e) {
101-
onParseError(event.newValue, e)
102-
return
103-
}
104-
const processedVal = beforeRead(newVal)
105-
106-
set(processedVal)
95+
const initial = maybeLoadInitial()
96+
const store = internal(initial, (set) => {
97+
if (browser && storageType == 'local' && syncTabs) {
98+
const handleStorage = (event: StorageEvent) => {
99+
if (event.key === key && event.newValue) {
100+
let newVal: any
101+
try {
102+
newVal = serializer.parse(event.newValue)
103+
} catch (e) {
104+
onParseError(event.newValue, e)
105+
return
107106
}
108-
}
107+
const processedVal = beforeRead(newVal)
109108

110-
window.addEventListener("storage", handleStorage)
111-
112-
return () => window.removeEventListener("storage", handleStorage)
109+
set(processedVal)
110+
}
113111
}
114-
})
115112

116-
const { subscribe, set } = store
113+
window.addEventListener("storage", handleStorage)
117114

118-
stores[storageType][key] = {
119-
set(value: StoreType) {
120-
set(value)
121-
updateStorage(key, value)
122-
},
123-
update(callback: Updater<StoreType>) {
124-
return store.update((last) => {
125-
const value = callback(last)
126-
127-
updateStorage(key, value)
128-
129-
return value
130-
})
131-
},
132-
reset() {
133-
this.set(initialValue)
134-
},
135-
subscribe
115+
return () => window.removeEventListener("storage", handleStorage)
136116
}
117+
})
118+
119+
const { subscribe, set } = store
120+
const persistedStore = {
121+
set(value: StoreType) {
122+
set(value)
123+
updateStorage(key, value)
124+
},
125+
update(callback: Updater<StoreType>) {
126+
return store.update((last) => {
127+
const value = callback(last)
128+
129+
updateStorage(key, value)
130+
131+
return value
132+
})
133+
},
134+
reset() {
135+
this.set(initialValue)
136+
},
137+
subscribe
138+
}
139+
140+
if (browser) {
141+
stores[storageType][key] = persistedStore
137142
}
138-
return stores[storageType][key]
143+
144+
return persistedStore
139145
}

test/localStorageStore.test.ts renamed to test/localStorageStore.jsdom.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @vitest-environment jsdom
12
import { persisted, writable } from '../index'
23
import { get } from 'svelte/store'
34
import { expect, vi, beforeEach, describe, test, it } from 'vitest'

test/localStorageStore.node.test.ts

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// @vitest-environment node
2+
import { persisted, writable } from '../index'
3+
import { get } from 'svelte/store'
4+
import { expect, vi, beforeEach, describe, test, it } from 'vitest'
5+
6+
describe('writable()', () => {
7+
test('it works, but raises deprecation warning', () => {
8+
console.warn = vi.fn()
9+
10+
const store = writable('myKey2', 'initial')
11+
const value = get(store)
12+
13+
expect(value).toEqual('initial')
14+
expect(console.warn).toHaveBeenCalledWith(expect.stringMatching(/deprecated/))
15+
})
16+
})
17+
18+
describe('persisted()', () => {
19+
test('uses initial value if nothing in local storage', () => {
20+
const store = persisted('myKey', 123)
21+
const value = get(store)
22+
23+
expect(value).toEqual(123)
24+
})
25+
26+
describe('set()', () => {
27+
test('replaces old value', () => {
28+
const store = persisted('myKey3', '')
29+
store.set('new-value')
30+
const value = get(store)
31+
32+
expect(value).toEqual('new-value')
33+
})
34+
35+
test('adds new value', () => {
36+
const store = persisted('myKey4', '')
37+
store.set('new-value')
38+
const value = get(store)
39+
40+
expect(value).toEqual('new-value')
41+
})
42+
})
43+
44+
describe('update()', () => {
45+
test('replaces old value', () => {
46+
const store = persisted('myKey5', 123)
47+
store.update(n => n + 1)
48+
const value = get(store)
49+
50+
expect(value).toEqual(124)
51+
})
52+
53+
test('adds new value', () => {
54+
const store = persisted('myKey6', 123)
55+
store.update(n => n + 1)
56+
const value = get(store)
57+
58+
expect(value).toEqual(124)
59+
})
60+
})
61+
62+
describe('reset', () => {
63+
it('resets to initial value', () => {
64+
const store = persisted('myKey14', 123);
65+
store.set(456);
66+
store.reset();
67+
const value = get(store);
68+
69+
expect(value).toEqual(123);
70+
});
71+
});
72+
73+
describe('subscribe()', () => {
74+
it('publishes updates', () => {
75+
const store = persisted('myKey7', 123)
76+
const values: number[] = []
77+
const unsub = store.subscribe((value: number) => {
78+
if (value !== undefined) values.push(value)
79+
})
80+
store.set(456)
81+
store.set(999)
82+
83+
expect(values).toEqual([123, 456, 999])
84+
85+
unsub()
86+
})
87+
})
88+
89+
it("doesn't handle duplicate stores with the same key", () => {
90+
const store1 = persisted('same-key', 1)
91+
const values1: number[] = []
92+
93+
const unsub1 = store1.subscribe(value => {
94+
values1.push(value)
95+
})
96+
97+
store1.set(2)
98+
99+
const store2 = persisted('same-key', 99)
100+
const values2: number[] = []
101+
102+
const unsub2 = store2.subscribe(value => {
103+
values2.push(value)
104+
})
105+
106+
store1.set(3)
107+
store2.set(4)
108+
109+
expect(values1).toEqual([1, 2, 3])
110+
expect(values2).toEqual([99, 4])
111+
expect(get(store1)).not.toEqual(get(store2))
112+
113+
expect(store1).not.toEqual(store2)
114+
115+
unsub1()
116+
unsub2()
117+
})
118+
119+
it('allows custom serialize/deserialize functions', () => {
120+
const serializer = {
121+
stringify: (set: Set<number>) => JSON.stringify(Array.from(set)),
122+
parse: (json: string) => new Set(JSON.parse(json)),
123+
}
124+
125+
const testSet = new Set([1, 2, 3])
126+
127+
const store = persisted('myKey11', testSet, { serializer })
128+
const value = get(store)
129+
130+
store.update(d => d.add(4))
131+
132+
expect(value).toEqual(testSet)
133+
})
134+
135+
it('lets you switch storage type', () => {
136+
const store = persisted('myKey12', 'foo', {
137+
storage: 'session'
138+
})
139+
140+
store.set('bar')
141+
142+
expect(get(store)).toEqual('bar')
143+
})
144+
})

test/readDomExceptions.test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @vitest-environment jsdom
12
import { persisted } from '../index'
23
import { expect, vi, beforeEach, describe, it } from 'vitest'
34

vite.config.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { defineConfig } from 'vitest/config'
22

33
export default defineConfig({
44
test: {
5-
globals: true,
6-
environment: 'jsdom'
5+
globals: true
76
},
87
})

0 commit comments

Comments
 (0)