Repository: Kong/swrv
Pull request #231 (2021-12-30 21:23:46)

Remove staterefs from ref cache on unmount
# Goal

Eliminate a (slight) memory leak caused by the indefinite retention of `StateRef`s by the `REF_CACHE` when the time-to-live is `0` by removing a `StateRef` from the `REF_CACHE` when its component is unmounted (reported in #230).

# How Can This Be Tested?

1. Checkout the source branch at commit 856002c
2. `yarn`
3. `yarn test`
4. Observe the test failures.
5. Checkout the source branch at commit 2b4a1bc
6. `yarn test`
7. Verify the tests succeed.

EDIT: Corrected commit order.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/Kong/swrv?pullRequest=231) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/Kong/swrv?pullRequest=231) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/Kong/swrv?pullRequest=231) it.</sub>
darrenjennings: hey @jefrailey thanks for this patch! I've approved the build to run which is now failing. Can you fix the lint issues? Can approve afterwards. Thanks for the tests!
jefrailey: @darrenjennings Will do. It looks like it's all formatting issues--looks like I forgot to run `yarn lint`. I'm busy for the rest of the evening, but will fix this first thing tomorrow.

Patch:
diff --git a/src/use-swrv.ts b/src/use-swrv.ts
index e549caa..6d8df03 100644
--- a/src/use-swrv.ts
+++ b/src/use-swrv.ts
@@ -335,6 +335,10 @@ function useSWRV<Data = any, Error = any> (...args): IResponse<Data, Error> {
       document.removeEventListener('visibilitychange', revalidateCall, false)
       window.removeEventListener('focus', revalidateCall, false)
     }
+    const refCacheItem = REF_CACHE.get(keyRef.value)
+    if (refCacheItem) {
+      refCacheItem.data = refCacheItem.data.filter((ref) => ref !== stateRef)
+    }
   })
   if (IS_SERVER) {
     // make sure srwv exists in ssrContext


Test patch:
diff --git a/tests/use-swrv.spec.tsx b/tests/use-swrv.spec.tsx
index d412e24..50e0217 100644
--- a/tests/use-swrv.spec.tsx
+++ b/tests/use-swrv.spec.tsx
@@ -7,6 +7,33 @@ import { advanceBy, advanceTo, clear } from 'jest-date-mock'
 
 Vue.use(VueCompositionApi)
 
+// "Mock" the three caches that use-swrv.ts creates so that tests can make assertions about their contents.
+let mockDataCache
+let mockRefCache
+let mockPromisesCache
+
+jest.mock('../src/cache', () => {
+  const originalCache = jest.requireActual('../src/cache')
+  const Cache = originalCache.default
+  return {
+    __esModule: true,
+    default: jest
+      .fn()
+      .mockImplementationOnce(() => {
+        mockDataCache = new Cache()
+        return mockDataCache
+      })
+      .mockImplementationOnce(() => {
+        mockRefCache = new Cache()
+        return mockRefCache
+      })
+      .mockImplementationOnce(function () {
+        mockPromisesCache = new Cache()
+        return mockPromisesCache
+      })
+  }
+})
+
 jest.useFakeTimers()
 
 describe('useSWRV', () => {
@@ -1410,3 +1437,55 @@ describe('useSWRV - window events', () => {
     expect(vm.$el.textContent).toBe('first')
   })
 })
+
+describe('useSWRV - ref cache management', () => {
+  beforeEach(() => {
+    // Isolate the changes to the caches made by the tests in this block.
+    if (mockDataCache) {
+      mockDataCache.items = new Map()
+    }
+    if (mockRefCache) {
+      mockRefCache.items = new Map()
+    }
+    if (mockPromisesCache) {
+      mockPromisesCache.items = new Map()
+    }
+  })
+  it('useSwrv should remove stateRef from ref cache when the component is unmounted', async () => {
+    const key = 'key'
+    const fetchedValue = 'SWR'
+    const fetch = () => fetchedValue
+    const vm = new Vue({
+      template: '<div></div>',
+      setup () {
+        return useSWRV(key, fetch)
+      }
+    }).$mount()
+    expect(mockRefCache.get(key).data).toHaveLength(1)
+    vm.$destroy()
+    expect(mockRefCache.get(key).data).toHaveLength(0)
+  })
+
+  it('useSwrv should keep stateRefs from other components when its component is unmounted', async () => {
+    const key = 'key'
+    const fetchedValue = 'SWR'
+    const fetch = () => fetchedValue
+    const originalVm = new Vue({
+      template: '<div></div>',
+      setup () {
+        return useSWRV(key, fetch)
+      }
+    }).$mount()
+    expect(mockRefCache.get(key).data).toHaveLength(1)
+    // Create another Vue component that calls useSwrv with the same key.
+    new Vue({
+      template: '<div></div>',
+      setup () {
+        return useSWRV(key, fetch)
+      }
+    }).$mount()
+    expect(mockRefCache.get(key).data).toHaveLength(2)
+    originalVm.$destroy()
+    expect(mockRefCache.get(key).data).toHaveLength(1)
+  })
+})