Repository: zeit/swr
Pull request #219 (2020-01-02 09:27:04)

Allow use custom isEqual method
#147 
Sometimes it is necessary to confirm whether to change the cache based on specific fields.
Maybe add a config attribute?


Patch:
diff --git a/.eslintignore b/.eslintignore
index 14a4ef3..c631b88 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,3 @@
 dist/**/*
+esm/**/*
 node_modules
diff --git a/src/config.ts b/src/config.ts
index f395cf7..0ad426f 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,3 +1,4 @@
+import deepEqual from 'fast-deep-equal'
 import isDocumentVisible from './libs/is-document-visible'
 import isOnline from './libs/is-online'
 import {
@@ -49,6 +50,14 @@ function onErrorRetry(
   setTimeout(revalidate, timeout, opts)
 }
 
+// client side: need to adjust the config
+// based on the browser status
+// slow connection (<= 70Kbps)
+const slowConnection =
+  typeof window !== 'undefined' &&
+  navigator['connection'] &&
+  ['slow-2g', '2g'].indexOf(navigator['connection'].effectiveType) !== -1
+
 // config
 const defaultConfig: ConfigInterface = {
   // events
@@ -57,10 +66,10 @@ const defaultConfig: ConfigInterface = {
   onError: () => {},
   onErrorRetry,
 
-  errorRetryInterval: 5 * 1000,
+  errorRetryInterval: (slowConnection ? 10 : 5) * 1000,
   focusThrottleInterval: 5 * 1000,
   dedupingInterval: 2 * 1000,
-  loadingTimeout: 3 * 1000,
+  loadingTimeout: (slowConnection ? 5 : 3) * 1000,
 
   refreshInterval: 0,
   revalidateOnFocus: true,
@@ -68,22 +77,8 @@ const defaultConfig: ConfigInterface = {
   refreshWhenHidden: false,
   refreshWhenOffline: false,
   shouldRetryOnError: true,
-  suspense: false
-}
-
-if (typeof window !== 'undefined') {
-  // client side: need to adjust the config
-  // based on the browser status
-
-  // slow connection (<= 70Kbps)
-  if (navigator['connection']) {
-    if (
-      ['slow-2g', '2g'].indexOf(navigator['connection'].effectiveType) !== -1
-    ) {
-      defaultConfig.errorRetryInterval = 10 * 1000
-      defaultConfig.loadingTimeout = 5 * 1000
-    }
-  }
+  suspense: false,
+  compare: deepEqual
 }
 
 // Focus revalidate
diff --git a/src/types.ts b/src/types.ts
index c4d42ad..eb080eb 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -37,6 +37,8 @@ export interface ConfigInterface<
     revalidate: revalidateType,
     revalidateOpts: RevalidateOptionInterface
   ) => void
+
+  compare?: (a: Data | undefined, b: Data | undefined) => boolean
 }
 
 export interface RevalidateOptionInterface {
diff --git a/src/use-swr.ts b/src/use-swr.ts
index f873a5a..8dade51 100644
--- a/src/use-swr.ts
+++ b/src/use-swr.ts
@@ -1,4 +1,3 @@
-import deepEqual from 'fast-deep-equal'
 import {
   useCallback,
   useContext,
@@ -309,7 +308,7 @@ function useSWR<Data = any, Error = any>(
           // we don't have an error
           newState.error = undefined
         }
-        if (deepEqual(stateRef.current.data, newData)) {
+        if (config.compare(stateRef.current.data, newData)) {
           // deep compare to avoid extra re-render
           // do nothing
         } else {
@@ -384,7 +383,7 @@ function useSWR<Data = any, Error = any>(
     // update the state if the key changed or cache updated
     if (
       keyRef.current !== key ||
-      !deepEqual(currentHookData, latestKeyedData)
+      !config.compare(currentHookData, latestKeyedData)
     ) {
       dispatch({ data: latestKeyedData })
       keyRef.current = key
@@ -434,7 +433,7 @@ function useSWR<Data = any, Error = any>(
 
       if (
         typeof updatedData !== 'undefined' &&
-        !deepEqual(stateRef.current.data, updatedData)
+        !config.compare(stateRef.current.data, updatedData)
       ) {
         newState.data = updatedData
         needUpdate = true
@@ -471,7 +470,7 @@ function useSWR<Data = any, Error = any>(
 
     // set up reconnecting when the browser regains network connection
     let reconnect = null
-    if (addEventListener && config.revalidateOnReconnect) {
+    if (typeof addEventListener !== 'undefined' && config.revalidateOnReconnect) {
       reconnect = addEventListener('online', softRevalidate)
     }
 
@@ -501,7 +500,7 @@ function useSWR<Data = any, Error = any>(
         }
       }
 
-      if (removeEventListener && reconnect !== null) {
+      if (typeof removeEventListener !== 'undefined' && reconnect !== null) {
         removeEventListener('online', reconnect)
       }
     }


Test patch:
diff --git a/test/use-swr.test.tsx b/test/use-swr.test.tsx
index 5f3e901..df29a33 100644
--- a/test/use-swr.test.tsx
+++ b/test/use-swr.test.tsx
@@ -464,6 +464,47 @@ describe('useSWR - refresh', () => {
     })
     expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`)
   })
+
+  it('should allow use custom isEqual method', async () => {
+    function Page() {
+      const { data, revalidate } = useSWR(
+        'dynamic-11',
+        () => ({
+          timestamp: +new Date(),
+          version: '1.0'
+        }),
+        {
+          compare: function isEqual(a, b) {
+            if (a === b) {
+              return true
+            }
+            if (!a || !b) {
+              return false
+            }
+            return a.version === b.version
+          }
+        }
+      )
+
+      if (!data) {
+        return <div>loading</div>
+      }
+      return <button onClick={revalidate}>{data.timestamp}</button>
+    }
+
+    const { container } = render(<Page />)
+
+    expect(container.firstChild.textContent).toMatchInlineSnapshot(`"loading"`)
+    await waitForDomChange({ container })
+    const firstContent = container.firstChild.textContent
+    await act(() => {
+      // trigger revalidation
+      fireEvent.click(container.firstElementChild)
+      return new Promise(res => setTimeout(res, 1))
+    })
+    const secondContent = container.firstChild.textContent
+    expect(firstContent).toEqual(secondContent)
+  })
 })
 
 describe('useSWR - revalidate', () => {