Skip to content

Commit

Permalink
fix(utils/atomWithStorage): handle edge case of value inconsistency w…
Browse files Browse the repository at this point in the history
…ith subscription (pmndrs#1080)

* add failing test

* fix(utils/atomWithStorage): handle edge case of value inconsistency with subscription
  • Loading branch information
dai-shi authored Apr 4, 2022
1 parent 9658cc7 commit 9e336c6
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 4 deletions.
20 changes: 16 additions & 4 deletions src/utils/atomWithStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,23 @@ export function createJSONStorage<Value>(
export function createJSONStorage<Value>(
getStringStorage: () => AsyncStringStorage | SyncStringStorage
): AsyncStorage<Value> | SyncStorage<Value> {
let lastStr: string | undefined
let lastValue: any
return {
getItem: (key) => {
const value = getStringStorage().getItem(key)
if (value instanceof Promise) {
return value.then((v) => JSON.parse(v || ''))
const parse = (str: string | null) => {
str = str || ''
if (lastStr !== str) {
lastStr = str
lastValue = JSON.parse(str)
}
return lastValue
}
const str = getStringStorage().getItem(key)
if (str instanceof Promise) {
return str.then(parse)
}
return JSON.parse(value || '')
return parse(str)
},
setItem: (key, newValue) =>
getStringStorage().setItem(key, JSON.stringify(newValue)),
Expand Down Expand Up @@ -122,6 +132,8 @@ export function atomWithStorage<Value>(
let unsub: Unsubscribe | undefined
if (storage.subscribe) {
unsub = storage.subscribe(key, setAtom)
// in case it's updated before subscribing
setAtom(getInitialValue())
}
if (storage.delayInit) {
const value = getInitialValue()
Expand Down
35 changes: 35 additions & 0 deletions tests/utils/atomWithStorage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,23 @@ describe('atomWithStorage (sync)', () => {
},
setItem: (key: string, newValue: number) => {
storageData[key] = newValue
dummyStorage.listeners.forEach((listener) => {
listener(key, newValue)
})
},
removeItem: (key: string) => {
delete storageData[key]
},
listeners: new Set<(key: string, value: number) => void>(),
subscribe: (key: string, callback: (value: number) => void) => {
const listener = (k: string, value: number) => {
if (k === key) {
callback(value)
}
}
dummyStorage.listeners.add(listener)
return () => dummyStorage.listeners.delete(listener)
},
}

it('simple count', async () => {
Expand Down Expand Up @@ -60,6 +73,28 @@ describe('atomWithStorage (sync)', () => {
await findByText('count: 1')
expect(storageData.count).toBeUndefined()
})

it('storage updates before mount (#1079)', async () => {
dummyStorage.setItem('count', 10)
const countAtom = atomWithStorage('count', 1, dummyStorage)

const Counter = () => {
const [count] = useAtom(countAtom)
// emulating updating before mount
if (dummyStorage.getItem('count') !== 9) {
dummyStorage.setItem('count', 9)
}
return <div>count: {count}</div>
}

const { findByText } = render(
<Provider>
<Counter />
</Provider>
)

await findByText('count: 9')
})
})

describe('atomWithStorage (async)', () => {
Expand Down

0 comments on commit 9e336c6

Please sign in to comment.