Repository: Kong/swrv
Pull request #316 (2022-10-16 10:05:53)

Is loading
PR for https://github.com/Kong/swrv/issues/208

Comments:

1. Some cases of setting `isLoading = false` are not covered by tests. They follow analogical lines for `isValidating`. I only found a test case for one of them (the second changed line) and I am not sure whether `isLoading` is relevant there - if so, pls let me know I will try to extend the test case. The other two `isValidating = false` however do not break any tests when commented out. All these three lines are is a separate (second) commit.
2. If this gets merged, SWRV docs site would need to be updated too.

(Note: this is a second PR for #208. The first one (https://github.com/Kong/swrv/pull/284) has been created against the former `next` branch.)


adamdehaven: Revisiting this now, it may make sense to add; `swr` has since added as well and thoroughly explains the differences between `isValidating` and `isLoading`: https://github.com/vercel/swr/pull/1928
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy request for *docs-swrv* pending review.
[Visit the deploys page to approve it](https://app.netlify.com/sites/docs-swrv/deploys)

|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 5caf00be8a11166567d07ebf99e61e71781ac2b5 |

Patch:
diff --git a/src/types.ts b/src/types.ts
index 70c0220..2e6798e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -33,6 +33,7 @@ export interface IResponse<Data = any, Error = any> {
   data: Ref<Data | undefined>
   error: Ref<Error | undefined>
   isValidating: Ref<boolean>
+  isLoading: Ref<boolean>
   mutate: (data?: fetcherFn<Data>, opts?: revalidateOptions) => Promise<void>
 }
 
diff --git a/src/use-swrv.ts b/src/use-swrv.ts
index 25666d9..dde7190 100644
--- a/src/use-swrv.ts
+++ b/src/use-swrv.ts
@@ -34,7 +34,9 @@ import webPreset from './lib/web-preset'
 import SWRVCache from './cache'
 import { IConfig, IKey, IResponse, fetcherFn, revalidateOptions } from './types'
 
-type StateRef<Data, Error> = { data: Data, error: Error, isValidating: boolean, revalidate: Function, key: any };
+type StateRef<Data, Error> = {
+  data: Data, error: Error, isValidating: boolean, isLoading: boolean, revalidate: Function, key: any
+};
 
 const DATA_CACHE = new SWRVCache<Omit<IResponse, 'mutate'>>()
 const REF_CACHE = new SWRVCache<StateRef<any, any>[]>()
@@ -131,6 +133,7 @@ const mutate = async <Data>(key: string, res: Promise<Data> | Data, cache = DATA
       }
       r.error = newData.error
       r.isValidating = newData.isValidating
+      r.isLoading = newData.isValidating
 
       const isLast = idx === refs.length - 1
       if (!isLast) {
@@ -228,6 +231,7 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
       data: undefined,
       error: undefined,
       isValidating: true,
+      isLoading: true,
       key: null
     }) as StateRef<Data, Error>
   }
@@ -244,6 +248,7 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
     const newData = cacheItem && cacheItem.data
 
     stateRef.isValidating = true
+    stateRef.isLoading = !newData
     if (newData) {
       stateRef.data = newData.data
       stateRef.error = newData.error
@@ -256,6 +261,7 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
       (opts?.forceRevalidate !== undefined && !opts?.forceRevalidate)
     ) {
       stateRef.isValidating = false
+      stateRef.isLoading = false
       return
     }
 
@@ -267,6 +273,7 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
 
       if (!shouldRevalidate) {
         stateRef.isValidating = false
+        stateRef.isLoading = false
         return
       }
     }
@@ -282,6 +289,7 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
         await mutate(keyVal, promiseFromCache.data, config.cache, ttl)
       }
       stateRef.isValidating = false
+      stateRef.isLoading = false
       PROMISES_CACHE.delete(keyVal)
       if (stateRef.error !== undefined) {
         const shouldRetryOnError = !unmounted && config.shouldRetryOnError && (opts ? opts.shouldRetryOnError : true)


Test patch:
diff --git a/tests/use-swrv.spec.tsx b/tests/use-swrv.spec.tsx
index 634db14..a28c441 100755
--- a/tests/use-swrv.spec.tsx
+++ b/tests/use-swrv.spec.tsx
@@ -799,6 +799,77 @@ describe('useSWRV - loading', () => {
 
     expect(wrapper.text()).toBe(':ready')
   })
+
+  it('should indicate cached data from another key with isLoading false', async () => {
+    const key = ref(1)
+    const wrapper = mount(defineComponent({
+      template: `<div>data: {{ String(data) }}, isValidating: {{ isValidating }}, isLoading: {{ isLoading }}</div>
+        <button id="inc" v-on:click="inc"/><button id="dec" v-on:click="dec"/>`,
+      setup () {
+        const { data, isValidating, isLoading } = useSWRV(() => 'key-' + key.value, async () => {
+          await loadData()
+          return 'data-' + key.value
+        }, { dedupingInterval: 0 })
+
+        const inc = () => {
+          key.value++
+        }
+        const dec = () => {
+          key.value--
+        }
+
+        return { inc, dec, data, isValidating, isLoading }
+      }
+    }))
+
+    // key 1
+    // first load ever, cache empty, no data => isLoading true
+    expect(wrapper.text()).toBe('data: undefined, isValidating: true, isLoading: true')
+    timeout(100)
+    await tick(2)
+    // key-1 data loaded
+    expect(wrapper.text()).toBe('data: data-1, isValidating: false, isLoading: false')
+
+    // key: 1 -> 2
+    // first load for a key, key cache empty, data from previous key => isLoading true
+    wrapper.find('#inc').trigger('click')
+    await tick(2)
+    expect(wrapper.text()).toBe('data: data-1, isValidating: true, isLoading: true')
+    timeout(100)
+    await tick(2)
+    // data loaded
+    expect(wrapper.text()).toBe('data: data-2, isValidating: false, isLoading: false')
+
+    // key: 2 -> 1
+    // next load for a key, data from key cache => isLoading false
+    wrapper.find('#dec').trigger('click')
+    await tick(2)
+    expect(wrapper.text()).toBe('data: data-1, isValidating: true, isLoading: false')
+    timeout(100)
+    await tick(2)
+    // data loaded
+    expect(wrapper.text()).toBe('data: data-1, isValidating: false, isLoading: false')
+
+    // key: 1 -> 2
+    // next load for a key, data from key cache => isLoading false
+    wrapper.find('#inc').trigger('click')
+    await tick(2)
+    expect(wrapper.text()).toBe('data: data-2, isValidating: true, isLoading: false')
+    timeout(100)
+    await tick(2)
+    // data loaded
+    expect(wrapper.text()).toBe('data: data-2, isValidating: false, isLoading: false')
+
+    // key: 2 -> 3
+    // first load for a key, key cache empty, data from previous key => isLoading true
+    wrapper.find('#inc').trigger('click')
+    await tick(2)
+    expect(wrapper.text()).toBe('data: data-2, isValidating: true, isLoading: true')
+    timeout(100)
+    await tick(2)
+    // data loaded
+    expect(wrapper.text()).toBe('data: data-3, isValidating: false, isLoading: false')
+  })
 })
 
 describe('useSWRV - mutate', () => {