Repository: zeit/swr (ts)

Issue:
softRevalidate error when no fetcher or default fetcher provided.
I'm trying to use swr to read in a value without a fetcher, since I know the fetch was started elsewhere (tab one).

Using the [provided demo](https://codesandbox.io/s/eager-field-zs7v8), tab between _one_ and _three_.  Eventually you'll see the following error:

```
index.js:1375 Result Error TypeError: fn is not a function
    at use-swr.js:213
    at softRevalidate (use-swr.js:321)
```
keyFunction type declaration incorrect
keyFunction's type is `declare type keyFunction = () => string;`, it can't accept a null return value or array value, which is conflict with example

Pull request description:
Improvements
- fix #128
- use badgen.net
- allow arg array returning function as key
- allow fetcher to be undefined, fix #123
- change splice to swap (which is O(n) → O(1))


Patch:
diff --git a/README.md b/README.md
index 2747b59..900b25a 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,16 @@
 
 <p align="center">
   <a aria-label="ZEIT logo" href="https://github.com/zeit">
-    <img src="https://img.shields.io/badge/MADE%20BY%20ZEIT-000000.svg?logo=ZEIT&labelColor=000000&logoWidth=12">
+    <img src="https://badgen.net/badge/icon/MADE%20BY%20ZEIT?icon=zeit&label&color=black&labelColor=black">
   </a>
   <a aria-label="NPM version" href="https://www.npmjs.com/package/swr">
-    <img alt="" src="https://img.shields.io/npm/v/swr">
+    <img alt="" src="https://badgen.net/npm/v/swr">
   </a>
   <a aria-label="Package size" href="https://bundlephobia.com/result?p=swr">
-    <img alt="" src="https://img.shields.io/bundlephobia/minzip/swr">
+    <img alt="" src="https://badgen.net/bundlephobia/minzip/swr">
   </a>
   <a aria-label="License" href="https://github.com/zeit/swr/blob/master/LICENSE">
-    <img alt="" src="https://img.shields.io/npm/l/swr">
+    <img alt="" src="https://badgen.net/npm/license/swr">
   </a>
 </p>
 
@@ -107,6 +107,7 @@ const { data, error, isValidating, revalidate } = useSWR(key, fetcher, options)
 
 - `suspense = false`: enable React Suspense mode [(details)](#suspense-mode)
 - `fetcher = undefined`: the default fetcher function
+- `initialData`: initial data to be returned (note: This is per-hook)
 - `revalidateOnFocus = true`: auto revalidate when window gets focused
 - `refreshInterval = 0`: polling interval (disabled by default)
 - `refreshWhenHidden = false`: polling when the window is invisible (if `refreshInterval` is enabled)
@@ -119,7 +120,6 @@ const { data, error, isValidating, revalidate } = useSWR(key, fetcher, options)
 - `onSuccess`: callback function when a request finishs successfully
 - `onError`: callback function when a request returns an error
 - `onErrorRetry`: handler for [error retry](#error-retries)
-- `initialData`: initial data to be returned (note: This is per-hook)
 
 When under a slow network (2G, <= 70Kbps), `errorRetryInterval` will be 10s, and
 `loadingTimeout` will be 5s by default.
diff --git a/src/types.ts b/src/types.ts
index 868f49f..98c7a2d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -44,8 +44,8 @@ export interface RevalidateOptionInterface {
   dedupe?: boolean
 }
 
-type keyFunction = () => string
-export type keyInterface = string | keyFunction | any[] | null
+type keyFunction = () => string | any[] | null
+export type keyInterface = keyFunction | string | any[] | null
 export type updaterInterface<Data = any, Error = any> = (
   shouldRevalidate?: boolean,
   data?: Data,
diff --git a/src/use-swr.ts b/src/use-swr.ts
index 6399c23..ff9d1e3 100644
--- a/src/use-swr.ts
+++ b/src/use-swr.ts
@@ -46,24 +46,26 @@ const useIsomorphicLayoutEffect = IS_SERVER ? useEffect : useLayoutEffect
 
 // TODO: introduce namepsace for the cache
 const getErrorKey = key => (key ? 'err@' + key : '')
-const getKeyArgs = _key => {
-  let key: string
+const getKeyArgs = key => {
   let args = null
-  if (typeof _key === 'function') {
+  if (typeof key === 'function') {
     try {
-      key = _key()
+      key = key()
     } catch (err) {
       // dependencies not ready
       key = ''
     }
-  } else if (Array.isArray(_key)) {
+  }
+
+  if (Array.isArray(key)) {
     // args array
-    key = hash(_key)
-    args = _key
+    args = key
+    key = hash(key)
   } else {
     // convert null to ''
-    key = String(_key || '')
+    key = String(key || '')
   }
+
   return [key, args]
 }
 
@@ -187,7 +189,7 @@ function useSWR<Data = any, Error = any>(
     async (
       revalidateOpts: RevalidateOptionInterface = {}
     ): Promise<boolean> => {
-      if (!key) return false
+      if (!key || !fn) return false
       if (unmountedRef.current) return false
       revalidateOpts = Object.assign({ dedupe: false }, revalidateOpts)
 
@@ -441,12 +443,22 @@ function useSWR<Data = any, Error = any>(
       unmountedRef.current = true
 
       if (onFocus && FOCUS_REVALIDATORS[key]) {
-        const index = FOCUS_REVALIDATORS[key].indexOf(onFocus)
-        if (index >= 0) FOCUS_REVALIDATORS[key].splice(index, 1)
+        const revalidators = FOCUS_REVALIDATORS[key]
+        const index = revalidators.indexOf(onFocus)
+        if (index >= 0) {
+          // 10x faster than splice
+          // https://jsperf.com/array-remove-by-index
+          revalidators[index] = revalidators[revalidators.length - 1]
+          revalidators.pop()
+        }
       }
       if (CACHE_REVALIDATORS[key]) {
-        const index = CACHE_REVALIDATORS[key].indexOf(onUpdate)
-        if (index >= 0) CACHE_REVALIDATORS[key].splice(index, 1)
+        const revalidators = CACHE_REVALIDATORS[key]
+        const index = revalidators.indexOf(onUpdate)
+        if (index >= 0) {
+          revalidators[index] = revalidators[revalidators.length - 1]
+          revalidators.pop()
+        }
       }
 
       if (timeout !== null) {


Test patch:
diff --git a/test/use-swr.test.tsx b/test/use-swr.test.tsx
index f6955c2..ed934da 100644
--- a/test/use-swr.test.tsx
+++ b/test/use-swr.test.tsx
@@ -223,6 +223,25 @@ describe('useSWR', () => {
     )
   })
 
+  it('should accept function returning args', async () => {
+    const obj = { v: 'hello' }
+    const arr = ['world']
+
+    function Page() {
+      const { data } = useSWR(
+        () => ['args-3', obj, arr],
+        (a, b, c) => a + b.v + c[0]
+      )
+
+      return <div>{data}</div>
+    }
+
+    const { container } = render(<Page />)
+
+    await waitForDomChange({ container })
+    expect(container.textContent).toMatchInlineSnapshot(`"args-3helloworld"`)
+  })
+
   it('should accept initial data', async () => {
     function Page() {
       const { data } = useSWR('initial-data-1', () => 'SWR', {