diff --git a/src/cache/adapters/localStorage.ts b/src/cache/adapters/localStorage.ts index f8a7bf9..e3f3c9d 100644 --- a/src/cache/adapters/localStorage.ts +++ b/src/cache/adapters/localStorage.ts @@ -1,4 +1,5 @@ import SWRVCache, { ICacheItem } from '..' +import { IKey } from '../../types' /** * LocalStorage cache adapter for swrv data cache. @@ -31,7 +32,7 @@ export default class LocalStorageCache extends SWRVCache { return undefined } - set (k: string, v: any, ttl: number) { + set (k: IKey, v: any, ttl: number) { let payload = {} const _key = this.serializeKey(k) const timeToLive = ttl || this.ttl diff --git a/src/cache/index.ts b/src/cache/index.ts index ff06e24..9abb851 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -39,12 +39,12 @@ export default class SWRVCache { return serializeKeyDefault(key) } - get (k: string): ICacheItem { + get (k: IKey): ICacheItem { const _key = this.serializeKey(k) return this.items.get(_key) } - set (k: string, v: any, ttl: number) { + set (k: IKey, v: any, ttl: number) { const _key = this.serializeKey(k) const timeToLive = ttl || this.ttl const now = Date.now() diff --git a/src/use-swrv.ts b/src/use-swrv.ts index 7d966b6..5574039 100644 --- a/src/use-swrv.ts +++ b/src/use-swrv.ts @@ -108,11 +108,15 @@ function resolveRetryFlag ({ return defaultConfig.shouldRetryOnError as boolean } +function isSameSerializedKey (keyA: IKey, keyB: IKey, cache = DATA_CACHE): boolean { + return cache.serializeKey(keyA) === cache.serializeKey(keyB) +} + /** * Main mutation function for receiving data from promises to change state and * set data cache */ -const mutate = async (key: string, res: Promise | Data, cache = DATA_CACHE, ttl = defaultConfig.ttl) => { +const mutate = async (key: IKey, res: Promise | Data, cache = DATA_CACHE, ttl = defaultConfig.ttl) => { let data, error, isValidating if (isPromise(res)) { @@ -145,7 +149,7 @@ const mutate = async (key: string, res: Promise | Data, cache = DATA // This filter fixes #24 race conditions to only update ref data of current // key, while data cache will continue to be updated if revalidation is // fired - let refs = stateRef.data.filter(r => r.key === key) + let refs = stateRef.data.filter(r => isSameSerializedKey(r.key, key, cache)) refs.forEach((r, idx) => { if (typeof newData.data !== 'undefined') { @@ -306,9 +310,14 @@ function useSWRV (...args): IResponse { } else { await mutate(keyVal, promiseFromCache.data, config.cache, ttl) } + PROMISES_CACHE.delete(keyVal) + + if (!isSameSerializedKey(stateRef.key, keyVal, config.cache)) { + return + } + stateRef.isValidating = false stateRef.isLoading = false - PROMISES_CACHE.delete(keyVal) if (stateRef.error !== undefined) { const configAllows = resolveRetryFlag({ shouldRetry: config.shouldRetryOnError, error: stateRef.error }) const optsAllows = resolveRetryFlag({ @@ -434,6 +443,9 @@ function useSWRV (...args): IResponse { } stateRef.key = val stateRef.isValidating = Boolean(val) + if (!val) { + stateRef.isLoading = false + } setRefCache(keyRef.value, stateRef, ttl) if (!IS_SERVER && !isHydrated && keyRef.value) { diff --git a/tests/use-swrv.spec.tsx b/tests/use-swrv.spec.tsx index 396a859..7c049f7 100755 --- a/tests/use-swrv.spec.tsx +++ b/tests/use-swrv.spec.tsx @@ -153,6 +153,24 @@ describe('useSWRV', () => { expect(wrapper.text()).toBe('hello, SWR') }) + it('should update refs when mutating a computed array key', async () => { + const id = ref('1') + const wrapper = mount(defineComponent({ + template: '
{{ data }}
', + setup () { + const computedKey = computed(() => ['computed-array-key', id.value]) + return useSWRV(computedKey, null) + } + })) + + expect(wrapper.text()).toBe('') + + await mutate(['computed-array-key', '1'], 'SWR') + await tick() + + expect(wrapper.text()).toBe('SWR') + }) + it('should accept object args', async () => { const obj = { v: 'hello' } const arr = ['world'] @@ -800,6 +818,29 @@ describe('useSWRV - loading', () => { expect(wrapper.text()).toBe(':ready') }) + it('should clear loading state when key becomes nullish during revalidation', async () => { + const key = ref('loading-key') + const wrapper = mount(defineComponent({ + template: '
isValidating: {{ isValidating }}, isLoading: {{ isLoading }}
', + setup () { + const { isValidating, isLoading } = useSWRV(() => key.value, () => new Promise(res => setTimeout(() => res('SWR'), 200))) + return { isValidating, isLoading } + } + })) + + expect(wrapper.text()).toBe('isValidating: true, isLoading: true') + + key.value = null + await tick(2) + + expect(wrapper.text()).toBe('isValidating: false, isLoading: false') + + timeout(200) + await tick(2) + + expect(wrapper.text()).toBe('isValidating: false, isLoading: false') + }) + it('should indicate cached data from another key with isLoading false', async () => { const key = ref(1) const wrapper = mount(defineComponent({ @@ -870,6 +911,36 @@ describe('useSWRV - loading', () => { // data loaded expect(wrapper.text()).toBe('data: data-3, isValidating: false, isLoading: false') }) + + it('should keep validating when an old key resolves after the current key changes', async () => { + const key = ref('old') + const wrapper = mount(defineComponent({ + template: '
data: {{ data }}, isValidating: {{ isValidating }}
', + setup () { + const { data, isValidating } = useSWRV(() => key.value, currentKey => { + const delay = currentKey === 'old' ? 200 : 400 + return new Promise(res => setTimeout(() => res(currentKey), delay)) + }) + + return { data, isValidating } + } + })) + + expect(wrapper.text()).toBe('data: , isValidating: true') + + timeout(100) + key.value = 'new' + await tick(2) + expect(wrapper.text()).toBe('data: , isValidating: true') + + timeout(100) + await tick(2) + expect(wrapper.text()).toBe('data: , isValidating: true') + + timeout(300) + await tick(2) + expect(wrapper.text()).toBe('data: new, isValidating: false') + }) }) describe('useSWRV - mutate', () => {