From 604d7ac3c462acb987d702d338e6d12efbb08869 Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Fri, 24 Apr 2026 02:04:36 +0530 Subject: [PATCH 1/2] fix(use-swrv): compare serialized keys during revalidation Compare cache keys through the cache serializer when applying mutations so computed array keys and other structurally equivalent keys update their refs. Also avoid clearing validation/loading state when an older in-flight request resolves after a hook has moved to a different key. Signed-off-by: Asish Kumar --- src/cache/adapters/localStorage.ts | 3 +- src/cache/index.ts | 4 +-- src/use-swrv.ts | 15 ++++++++-- tests/use-swrv.spec.tsx | 48 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) 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..ca6446b 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({ diff --git a/tests/use-swrv.spec.tsx b/tests/use-swrv.spec.tsx index 396a859..4891d1b 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'] @@ -870,6 +888,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', () => { From 8660a69f59240aa06d2eaec5840e258633e75a42 Mon Sep 17 00:00:00 2001 From: Asish Kumar Date: Fri, 24 Apr 2026 02:12:55 +0530 Subject: [PATCH 2/2] fix(use-swrv): clear loading when key becomes falsy Signed-off-by: Asish Kumar --- src/use-swrv.ts | 3 +++ tests/use-swrv.spec.tsx | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/use-swrv.ts b/src/use-swrv.ts index ca6446b..5574039 100644 --- a/src/use-swrv.ts +++ b/src/use-swrv.ts @@ -443,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 4891d1b..7c049f7 100755 --- a/tests/use-swrv.spec.tsx +++ b/tests/use-swrv.spec.tsx @@ -818,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({