Repository: zeit/swr
Pull request #225 (2020-01-10 16:19:30)

revalidateOnMount flag that disable automatic revalidation on mount
Hi there,
we're currently using useSWR but we have found conditional revalidation a bit limiting.

For example we have some occurences where we want to trigger revalidation only after a certain action is being taken (could be a button click).
With current conditional revalidation techniques we are forced to use an additional state just to prevent first validation on mount.

This PR aims to add a simple autoLoad (or whatever other name) flag in the options so that automatic revalidation on mount can be prevented and we can simply trigger it directly in a callback (for example a button onClick).

Best,
Gabriele

sergiodxa: Could I propose rename from `autoLoad` to `revalidateInitialData`?
ciak0: Ok, will do it!
ciak0: > Could I propose rename from `autoLoad` to `revalidateInitialData`?

Just pushed option with new name and updated documentation README that I previously forgot.
Let me know what you think about it @sergiodxa @quietshu!

Cheers
ciak0: Uhmm...yes the purpose is to avoid revalidate on mount, ignoring
initialData, so I guess the latter name is better.

Regarding behavior:
if the flag is set to false I expect that revalidate doesn't occur at all
so that condition is probably an "or". I'll double check tomorrow!

Cheers

Il giorno mar 28 gen 2020 alle 22:57 Sergio Xalambrí <
notifications@github.com> ha scritto:

> *@sergiodxa* commented on this pull request.
>
> Based on the test case, should this flag prevent SWR to revalidate the
> data provided as initialData only or also preven the revalidation if
> there is an already cached value for the given key?
>
> If it's the last, then ignore my comments in the code and I would propose
> to use revalidateOnMount as name to describe better what this does.
> ------------------------------
>
> In README.md <https://github.com/zeit/swr/pull/225#discussion_r372079588>:
>
> > @@ -109,6 +109,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)
>
> +- `revalidateInitialData = true`: automatically revalidate when component is mounted
>
>
> ⬇️ Suggested change
>
> -- `revalidateInitialData = true`: automatically revalidate when component is mounted
>
> +- `revalidateInitialData = true`: automatically revalidate when component is mounted (if `initialData` is provided)
>
>
> ------------------------------
>
> In src/use-swr.ts
> <https://github.com/zeit/swr/pull/225#discussion_r372080574>:
>
> > @@ -394,7 +394,7 @@ function useSWR<Data = any, Error = any>(
>
>      const softRevalidate = () => revalidate({ dedupe: true })
>
>
>
>      // trigger a revalidation
>
> -    if (!config.initialData) {
>
> +    if (!config.initialData && config.revalidateInitialData) {
>
>
> Shouldn't this be or?
> ⬇️ Suggested change
>
> -    if (!config.initialData && config.revalidateInitialData) {
>
> +    if (!config.initialData || config.revalidateInitialData) {
>
>
> If there is not initial data or want to revalidate the initial data, then
> trigger a revalidation
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/zeit/swr/pull/225?email_source=notifications&email_token=ACXXQR6B7CT6VZTXECVCXCDRACS5HA5CNFSM4KFKDMS2YY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOCTMHSXA#pullrequestreview-349731164>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ACXXQR2GRRHSRL7TL4M2AHLRACS5HANCNFSM4KFKDMSQ>
> .
>

ciak0: @sergiodxa @quietshu sorry for the delay, just updated PR with suggested hints and made it more clear. Let me know if you think is good or you have any other comment!

Best,
Gabriele
ciak0: Yeah you're right. I got your point!
Makes absolutely sense to me, I'm gonna do it asap!

Il giorno mar 7 apr 2020 alle 19:00 Ingo Claro <notifications@github.com>
ha scritto:

> *@ingoclaro* commented on this pull request.
> ------------------------------
>
> In src/use-swr.ts
> <https://github.com/zeit/swr/pull/225#discussion_r404967917>:
>
> > @@ -393,7 +393,7 @@ function useSWR<Data = any, Error = any>(
>      const softRevalidate = () => revalidate({ dedupe: true })
>
>      // trigger a revalidation
> -    if (!config.initialData) {
> +    if (!config.initialData && config.revalidateOnMount) {
>
> I just think that the variable name might be confusing, since it won't
> revalidate if initialData is provided, so from api usage perspective if I'm
> setting it as true explicitly (even with initialData), and it's not
> happening, it's weird, hence my suggestion to have it as undefined by
> default = current behavior.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/zeit/swr/pull/225#discussion_r404967917>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ACXXQR57CU7PZW3FVYLYSX3RLNL3HANCNFSM4KFKDMSQ>
> .
>

ciak0: @shuding @pacocoursey @Timer any news? do I have to do something else?
sergiodxa: @shuding is there something else @ciak0 needs to do to merge this?
shuding: ...although there’re some other things we need to figure out for `useMutation`, but that’s another topic. 

Anyway, I think generally it’s great for `useSWR` to have a lot of options and be very customizable, but it’s better to not expose them directly to the users and have some helper abstractions. An example is `$.post` ⊂ `$.ajax`.
sergiodxa: 👍 I think it makes sense, I was thinking that this would allow us to have a `useSWRLazy` which will only trigger the fetch after you run mutate, but could still read from cache if it was already cached.
ciak0: Yeah great, makes sense!
I'll do it asap :)

Cheers,
Gab

Il giorno sab 9 mag 2020 alle 01:20 Sergio Xalambrí <
notifications@github.com> ha scritto:

> 👍 I think it makes sense, I was thinking that this would allow us to
> have a useSWRLazy which will only trigger the fetch after you run mutate,
> but could still read from cache if it was already cached.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/zeit/swr/pull/225#issuecomment-626060059>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ACXXQR423WBJ2TLO4XXX4ZTRQSHSXANCNFSM4KFKDMSQ>
> .
>


Patch:
diff --git a/README.md b/README.md
index b8eebf3..46d3d54 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,12 @@
 [![SWR](https://assets.zeit.co/image/upload/v1572289618/swr/banner.png)](https://swr.now.sh)
 
 <p align="center">
-  <a aria-label="ZEIT logo" href="https://github.com/zeit">
-    <img src="https://badgen.net/badge/icon/MADE%20BY%20ZEIT?icon=zeit&label&color=black&labelColor=black">
+
+  <a aria-label="Vercel logo" href="https://vercel.com">
+    <img src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=ZEIT&labelColor=000000">
   </a>
+  <br/>
+  
   <a aria-label="NPM version" href="https://www.npmjs.com/package/swr">
     <img alt="" src="https://badgen.net/npm/v/swr">
   </a>
@@ -110,6 +113,7 @@ const { data, error, isValidating, mutate } = 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)
+- `revalidateOnMount`: enable or disable automatic revalidation when component is mounted (by default revalidation occurs on mount when initialData is not set, use this flag to force behavior)
 - `revalidateOnFocus = true`: auto revalidate when window gets focused
 - `revalidateOnReconnect = true`: automatically revalidate when the browser regains a network connection (via `navigator.onLine`)
 - `refreshInterval = 0`: polling interval (disabled by default)
@@ -510,10 +514,10 @@ Together with techniques like [page prefetching](https://nextjs.org/docs#prefetc
 <br/>
 
 ## Authors
-- Shu Ding ([@shuding_](https://twitter.com/shuding_)) – [ZEIT](https://zeit.co)
-- Guillermo Rauch ([@rauchg](https://twitter.com/rauchg)) – [ZEIT](https://zeit.co)
-- Joe Haddad ([@timer150](https://twitter.com/timer150)) - [ZEIT](https://zeit.co)
-- Paco Coursey ([@pacocoursey](https://twitter.com/pacocoursey)) - [ZEIT](https://zeit.co)
+- Shu Ding ([@shuding_](https://twitter.com/shuding_)) – [Vercel](https://vercel.com)
+- Guillermo Rauch ([@rauchg](https://twitter.com/rauchg)) – [Vercel](https://vercel.com)
+- Joe Haddad ([@timer150](https://twitter.com/timer150)) - [Vercel](https://vercel.com)
+- Paco Coursey ([@pacocoursey](https://twitter.com/pacocoursey)) - [Vercel](https://vercel.com)
 
 Thanks to Ryan Chen for providing the awesome `swr` npm package name!
 
diff --git a/src/types.ts b/src/types.ts
index a3f1b43..c163efa 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -13,6 +13,7 @@ export interface ConfigInterface<
   refreshWhenHidden?: boolean
   refreshWhenOffline?: boolean
   revalidateOnFocus?: boolean
+  revalidateOnMount?: boolean
   revalidateOnReconnect?: boolean
   shouldRetryOnError?: boolean
   fetcher?: Fn
diff --git a/src/use-swr.ts b/src/use-swr.ts
index f2d81db..b3b47a3 100644
--- a/src/use-swr.ts
+++ b/src/use-swr.ts
@@ -381,7 +381,10 @@ function useSWR<Data = any, Error = any>(
     const softRevalidate = () => revalidate({ dedupe: true })
 
     // trigger a revalidation
-    if (!config.initialData) {
+    if (
+      config.revalidateOnMount ||
+      (!config.initialData && config.revalidateOnMount === undefined)
+    ) {
       if (
         typeof latestKeyedData !== 'undefined' &&
         !IS_SERVER &&
@@ -586,13 +589,13 @@ function useSWR<Data = any, Error = any>(
         // so we need to match the latest key and data (fallback to `initialData`)
         get: function() {
           stateDependencies.current.error = true
-          return stateRef.current.error
+          return keyRef.current === key ? stateRef.current.error : initialError
         }
       },
       data: {
         get: function() {
           stateDependencies.current.data = true
-          return stateRef.current.data
+          return keyRef.current === key ? stateRef.current.data : initialData
         }
       },
       isValidating: {


Test patch:
diff --git a/test/use-swr.test.tsx b/test/use-swr.test.tsx
index a9b9545..9ff1e03 100644
--- a/test/use-swr.test.tsx
+++ b/test/use-swr.test.tsx
@@ -81,6 +81,35 @@ describe('useSWR', () => {
     )
   })
 
+  it('should not call fetch function when revalidateOnMount is false', async () => {
+    const fetch = jest.fn(() => 'SWR')
+
+    function Page() {
+      const { data } = useSWR('revalidateOnMount', fetch, {
+        revalidateOnMount: false
+      })
+      return <div>hello, {data}</div>
+    }
+
+    render(<Page />)
+    expect(fetch).not.toHaveBeenCalled()
+  })
+
+  it('should call fetch function when revalidateOnMount is true even if initialData is set', async () => {
+    const fetch = jest.fn(() => 'SWR')
+
+    function Page() {
+      const { data } = useSWR('revalidateOnMount', fetch, {
+        revalidateOnMount: true,
+        initialData: 'gab'
+      })
+      return <div>hello, {data}</div>
+    }
+
+    render(<Page />)
+    expect(fetch).toHaveBeenCalled()
+  })
+
   it('should dedupe requests by default', async () => {
     let count = 0
     const fetch = () => {
@@ -1409,4 +1438,46 @@ describe('useSWR - key', () => {
     await act(() => new Promise(res => setTimeout(res, 140)))
     expect(container.firstChild.textContent).toMatchInlineSnapshot(`"key-1"`) // 1, time=550
   })
+
+  it('should return undefined after key change when fetcher is synchronized', async () => {
+    const samples = {
+      '1': 'a',
+      '2': 'b',
+      '3': 'c'
+    }
+
+    function Page() {
+      const [sampleKey, setKey] = React.useState(1)
+      const { data } = useSWR(
+        `key-2-${sampleKey}`,
+        key => samples[key.replace('key-2-', '')]
+      )
+      return (
+        <div
+          onClick={() => {
+            setKey(sampleKey + 1)
+          }}
+        >
+          hello, {sampleKey}:{data}
+        </div>
+      )
+    }
+    const { container } = render(<Page />)
+    expect(container.firstChild.textContent).toMatchInlineSnapshot(
+      `"hello, 1:"`
+    )
+    await waitForDomChange({ container }) // mount
+    expect(container.firstChild.textContent).toMatchInlineSnapshot(
+      `"hello, 1:a"`
+    )
+    fireEvent.click(container.firstElementChild)
+    // first rerender on key change
+    expect(container.firstChild.textContent).toMatchInlineSnapshot(
+      `"hello, 2:"`
+    )
+    await act(() => new Promise(res => setTimeout(res, 100)))
+    expect(container.firstChild.textContent).toMatchInlineSnapshot(
+      `"hello, 2:b"`
+    )
+  })
 })