Data fetching and caching with SWR and Vue.js

Data fetching is a recurrent theme in modern JavaScript app development. The fetching itself is simple enough, all we really need is the fetch function or a third-party library like Axios. But in practice, we need to worry about more than just the fetching.

Whenever an app has to deal with remote data, we should be caching that data for performance purposes. And with caching, we need to be updating the cache at the right time. So, wouldn’t it be nice if we had a tool that combined fetching and cache management as one coherent concern?

There is a new wave of tools that employ a caching pattern called stale-while-revalidate, orSWR. Stale means that the data in the cache is outdated. Revalidate means to get a fresh copy of the data from the remote server to update the local cache.

Basically, stale-while-revalidate means whenever the app fetches data, the locally cached copy will be served to the app immediately, then a fetch will be made. And eventually, a fresh copy from the server will replace the cached copy, and in turn, updates the app.

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1.opt.1605901822713.jpg?alt=media&token=8ac44712-5aa1-4b76-ac67-4ce155fc7897

This caching policy is the best of both worlds because it makes the UI feel snappy without any lag, and the data stays up-to-date.

In this tutorial, we’ll check out an SWR implementation for Vue.js called SWRV (V for Vue). This library is meant to be used with Vue’s composition API. So, that means this tutorial does require some familiarity with the composition API, specifically about the setup function, ref, and computed property. (You can learn about all of these concepts in our Vue 3 Composition API course.)

Throughout this article, we’re going to create a Vue app and a JSON API, and then use SWRV to facilitate the data exchange between them.


New App

First, let’s create a Vue 3 app using the Vue CLI:

vue create swr-app

And choose Vue 3 in the prompt:

? Please pick a preset: 
  Default ([Vue 2] babel, eslint) 
❯ Default (Vue 3 Preview) ([Vue 3] babel, eslint) 
  Manually select features

(If you don’t see the Vue 3 option, make sure you have the latest Vue CLI installed.)

Finally, cd into the project and start the development server:

cd swr-app
npm run serve

For this Vue app, we’re going to fetch the data from an API server, so let’s create such a server now.


JSON Server

To quickly create a JSON server, we can make use the json-server NPM package.

We have to install it globally because it’s a CLI tool:

npm install -g json-server

(You might have to run this command with sudo if you run into some permission error.)

Next, prepare a db.json file, with some sample data.

{
  "articles": [
    {
      "title": "GraphQL Client with Vue.js",
      "author": "Andy",
      "link": "https://www.vuemastery.com/blog/part-3-client-side-graphql-with-vuejs",
      "id": 1
    },
    {
      "title": "TypeScript with Vue.js",
      "author": "Andy",
      "link": "https://www.vuemastery.com/blog/getting-started-with-typescript-and-vuejs",
      "id": 2
     }
  ]
}

This will be used as our “database”. json-server will use this json file to create a mock API server on the fly.

Now let’s start the server in the console.

json-server --watch db.json

(The --watch flag tells json-server to always serve the most up-to-date data from db.json.)

It should be up and running at port localhost:3000.

To test this new API server, we can open a browser console and just send the following request to get all the articles data.

fetch('[http://localhost:3000/articles](http://localhost:3000/articles)')
	.then(r => r.json())
	.then(data => console.log(data))

(json-server enables CORS by default, so you don’t actually have to make the request from the same port on the same domain.)

And we can make a POST request to add data to the server

fetch('[http://localhost:3000/articles](http://localhost:3000/articles)', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({
		"title": "VS Code for Vue Developers",
		"author": "Andy",
		"link": "https://www.vuemastery.com/blog/vs-code-for-vuejs-developers"
	})
})

If we open the db.json file we created earlier, we’ll see this new entry.

{
  "articles": [
    {
      "title": "GraphQL Client with Vue.js",
      "author": "Andy",
			"link": "https://www.vuemastery.com/blog/part-3-client-side-graphql-with-vuejs",
	    "id": 1
		},
    {
      "title": "TypeScript with Vue.js",
      "author": "Andy",
			"link": "https://www.vuemastery.com/blog/getting-started-with-typescript-and-vuejs",
      "id": 2
		},
    {
      "title": "VS Code for Vue Developers",
      "author": "Andy",
      "link": "https://www.vuemastery.com/blog/vs-code-for-vuejs-developers",
      "id": 3
    }
  ]
}

(json-server automatically assigns the id field for us, we didn’t have to send that over from the client-side.)

This means our API server is working!

If we want our Vue app’s development server interacting with this API server, we have to configure the development server as a proxy server. Proxy just means that it’s able to accept requests that it can’t process itself and pass these requests to another server. In our case, we want the Vue development server to pass all the API requests to the API server.

To set up the proxy, we just need to create a vue.config.js file in the project directory and put this inside:

📃 /vue.config.js

module.exports = {
  devServer: {
    proxy: 'http://localhost:3000'
  }
}

(localhost:3000 is where the API server is running.)

Finally, all the API server setup is done!


Fetching with SWRV

Now that we have a functional API server, let’s try using it in our Vue app with SWRV.

First, we have to install the SWRV library

npm install swrv

Inside the App.vue file, we’ll import the useSWRV function. This is the main function of the library.

📃 /src/App.vue

import useSWRV from 'swrv' 

export default {
  name: 'App',
	...

To demonstrate the core fetching and caching feature, let’s fetch some data from our JSON Server in the setup function.

📃 /src/App.vue

...
setup() {
  useSWRV('/articles', fetcher) 
},

SWRV doesn’t actually dictate how you’re getting the data, it just requires a function that returns a promise object. That’s the fetcher function as the second argument. So let’s create this function next. 📃 /src/App.vue

// ADD
const fetcher = function(url){
  return fetch(url).then(r => r.json())
}

export default {
  name: 'App',
	...

It’s really just a normal function that takes a url as the parameter, fetches the data from the server with the url, extracts the json from the server response, and finally, returns the json data as a promise object.

Since we provided the url as the first argument, SWRV will feed that url to our fetcher as a parameter. So what’s the point of giving the url to useSWRV just for it to get passed to the fetcher? Why not just decide what the url is inside the fetcher?

There are two reasons for this arrangement:

  • First, this fetcher is meant to be an abstraction over your fetch library of choice. You can use the generic fetch function, or a library like Axios. Then the fetcher function can be used for fetching from different URLs.
  • The second and more important reason is that the url string will be used as the cache key. SWRV will rely on this key to retrieve the corresponding data from the cache if it’s been cached previously.

Next, the useSWRV function will return a few items. For now, let’s just focus on the data and error.

📃 /src/App.vue

...
setup() {
  const { data, error } = useSWRV('/articles', fetcher)
},

Depending on the current status of the request, data can either contain the actually requested data or just undefined, while error can either contain an error message property or just undefined. Both of these variables are ref objects, so the data has to be accessed through data.value.

error is useful for displaying a “Sorry, there’s been an error” type of message conditionally.

We can rename them using object destructuring assignment, you don’t have to use these generic names if you don’t like.

📃 /src/App.vue

...
setup() {
  const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
},

And finally, return the two variables so we can use them in the template:

📃 /src/App.vue

...
setup() {
  const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
	
	// ADD
  return {
    articles,
    articlesError,
  }
},

Now, we can just display the content in the template (optionally, with a conditional error message):

📃 /src/App.vue

<template>
  <div>
    <p v-if="articlesError">Sorry, there's been an error</p>
    <ul>
      <li v-for="item in articles" v-bind:key="item.id">
        <a v-bind:href="item.link" target="blank">{{ item.title }}</a>
      </li>
    </ul>
  </div>
</template>

If you run the app in the browser, you should be seeing a bunch of article items:

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F2.opt.1605901822714.jpg?alt=media&token=45f11ede-275d-4c41-9d18-e59b354cda9f


To recap, here’s our code so far:

📃 /src/App.vue

<template>
  <div>
    <p v-if="articlesError">Sorry, there's been an error</p>
    <ul>
      <li v-for="item in articles" v-bind:key="item.id">
        <a v-bind:href="item.link" target="blank">{{ item.title }}</a>
        </li>
    </ul>
</div>
</template>

<script>
import useSWRV from 'swrv'

const fetcher = function(url){
  return fetch(url).then(r => r.json())
}

export default {
  name: 'App',
  setup() {
    const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
 
    return {
      articles,
      articlesError,
    }
  },
}
</script>

Caching

Next, let’s demonstrate how data are fetched from the local cache instead of the actual server. We will make two different components fetch from the same URL. The first one will hit the actual server, the second one will hit the cache instead.

To demonstrate the stale-while-revalidate concept, let’s create a second component, and put it inside the components folder.

📃 /src/components/Count.vue

<template>
  <div>
    <span>Article Count: {{ articles === undefined ? 0 : articles.length }}</span>
  </div>
</template>

<script>
import useSWRV from 'swrv'

const fetcher = function(url){
  return fetch(url).then(r => r.json())
}

export default {
  name: 'Count',
  setup() {
    const { data: articles } = useSWRV('/articles', fetcher)
 
    return {
      articles
    }
  },
}
</script>

This code is similar to App.vue in that they’re both fetching from the same URL /articles. But different from the first component since it’s displaying the number of articles instead of the articles themselves. I also left out the error for simplicity.

Since the fetcher is the same function for both components, we should extract it to its own file.

📃 /src/fetcher.js

const fetcher = function(url){
  return fetch(url).then(r => r.json())
}

export fetcher

In Count.vue, we can just import the fetcher:

📃 /src/components/Count.vue

import useSWRV from 'swrv'
import fetcher from '../fetcher.js' // ADD

export default {
  name: 'Count',

Back in App.vue, let’s import and use the new component and fetcher file:

📃 /src/App.vue

import useSWRV from 'swrv'
import Count from './components/Count.vue' // ADD
import fetcher from './fetcher.js'

// const fetcher = function(url){
//  return fetch(url).then(r => r.json())
// }

export default {
  name: 'App',
	components: { Count }, // ADD
  setup() {
    const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
 
    return {
      articles,
      articlesError,
    }
  },
}

Render the Count element in the template:

📃 /src/App.vue

			...
      </li>
    </ul>
    <Count></Count>
  </div>
</template>

If the cache works as expected, the fetcher function will run only once, then the second time that same API request is made inside the second component, the first request’s cached data will be served instead. So the second time, the fetcher function wouldn’t even get called.

We can confirm this by adding a console.log inside the fetcher function:

📃 /src/fetcher.js

const fetcher = function(url) {
  console.log('fetching ...'); // ADD
  return fetch(url).then(r => r.json());
} 

Run the app and check the browser console, you should be seeing just one log message from the fetcher.

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F3.opt.1605901829726.jpg?alt=media&token=3cb5da61-4918-46bb-9807-7c1b850ff0c5

The SWRV library is using the URL as the cache key to figure out whether or not you’re asking for the same data. Since both useSWRV calls are using the same URL as the cache key, they are recognized as the same thing by SWRV, so it just returned the cached data.


Revalidation

Revalidation means refreshing the cache so all elements on the page are showing the latest updated data. The default behavior of SWR is that every subsequent request with the same cache key as a previous request will reserve the cached stale version of the data first, then go on to do the fetch, then replace the cached stale version with the freshly updated version.

But as we demonstrated earlier, the second request only got served from the cache, it didn’t hit the server. That’s because the fetcher is asynchronous, so when the second fetch was initiated in the Count component, the first fetch hadn’t yet been resolved. Since it started before the first fetch was resolved, the second request isn’t considered “subsequent”; hence, it shared the same fetch response without doing another fetching over the network.

To get two separate fetches, we can set up a timer to render the Count component a few seconds later just to make sure it’s only rendered when the first fetch request has been resolved.

First we’ll set up a boolean ref, initialize it to false, and then set it to true in three seconds:

📃 /src/App.vue

import { ref } from 'vue' // ADD THIS
import useSWRV from 'swrv'
import Count from './components/Count.vue'
import fetcher from './fetcher'

export default {
  name: 'App',
  components: { Count },
  setup() {
    const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
	
    // ADD THIS	
    const showCount = ref(false) 
    setTimeout(() => showCount.value = true, 3000)

    return {
      articles,
      articlesError,
      showCount, // ADD THIS
    }
  },
}

(Since showCount is technically a ref object, we have to change it by changing its value property.)

And then use the showCount ref to conditionally render the Count element:

📃 /src/App.vue

		...
    </ul>
    <Count v-if="showCount"></Count>
</div>
</template>

Refresh the app in the browser, and pop open the browser console, you should see two console log messages from the fetcher function.

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F4.opt.1605901832482.jpg?alt=media&token=45aadf50-98c7-42d8-ac53-e9c4b1e8a741

This proves that subsequent fetches will still hit the server even when a cached version of the needed data is available.

Another interesting thing is: when you switch to another browser tab and switch back, you’ll see that the fetcher gets run again (check the console log messages). This is another default behavior of SWRV that all items in the cache get revalidated (refreshed) on focus. “Focus” means switching back to the page from another tab or window.


To recap, here’s our code in App.vue:

<template>
  <div>
     <p v-if="articlesError">sorry, there's been an error</p>
    <ul>
      <li v-for="item in articles" v-bind:key="item.id">
        <a v-bind:href="item.link" target="blank">{{ item.title }}</a>
      </li>
    </ul>
    <Count v-if="showCount"></Count>
  </div>
</template>

<script>
import { ref } from 'vue'
import useSWRV from 'swrv'
import Count from './components/Count.vue'
import fetcher from './fetcher'

export default {
  name: 'App',
  components: { Count },
  setup() {
    const { data: articles, error: articlesError } = useSWRV('/articles', fetcher)
    
    const showCount = ref(false)
    setTimeout(() => showCount.value = true, 3000)

    return {
      articles,
      articlesError,
      showCount,
    }
  },
}
</script>

Mutation

So we now know the cache gets refreshed from time to time when the window or browser tab is focused. We also have the choice of actively refreshing the cache. This is called a mutation.

Aside from data and error, useSWRV also returns a mutate function. We can use this function to trigger a revalidation for the associated cached data (articles in this case).

📃 /src/App.vue

setup() {
  const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)
	...

(For consistency, we renamed this mutate function to mutateArticles.)

Then, we can set up an event handler that calls the mutateArticle function:

📃 /src/App.vue

setup() {
  const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)

  // NEW
  function updateArticles(){
    mutateArticles() 
  }

	...
	
    return {
       articles,
       articlesError,
       showCount,
       updateArticles, // NEW
     }
},

And when the button is clicked, it will trigger the mutation:

📃 /src/App.vue

		...
    <button @click="updateArticles">Update Articles</button>
  </div>
</template>

When you click on this button in the browser, the fetcher will get called and update the cache with new data. And if the cache is updated, all the UI elements relying on the cache will get updated, too.

Since our server data doesn’t change by itself, you won’t see any difference when you click the button. But, if you still have that console.log inside the fetcher, you should see a log message ("fetching …") in the browser console every time you click on the button.

To clear up some confusions, the meaning of the term mutation here is different from what you might be used to. SWRV*'s* mutation isn’t about making a POST request to change some data on a remote server. Although, in practice, these two operations are usually paired together.


To recap, here’s our App.vue:

<template>
  <div>
    <p v-if="articlesError">sorry, there's been an error</p>
    <ul>
      <li v-for="item in articles" v-bind:key="item.id">
	<a v-bind:href="item.link" target="blank">{{ item.title }}</a>
      </li>
    </ul>
    <Count v-if="showCount"></Count>
    <button @click="updateArticles">Update Articles</button>
  </div>
</template>

<script>
import { ref } from 'vue'
import useSWRV from 'swrv'
import Count from './components/Count.vue'
import fetcher from './fetcher'

export default {
  name: 'App',
  components: { Count },
  setup() {
    const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)
    
    function updateArticles(){ 
      mutateArticles()
    }

    const showCount = ref(false)
    setTimeout(() => showCount.value = true, 3000)

    return {
      articles,
      articlesError,
      showCount,
      updateArticles,
    }
  },
}
</script>

POST Request

Instead of having a button to update the cache, let’s make the button send a POST request to add new data on the server.

First make the updateArticles function async and use await to make a POST request with fetch:

📃 /src/App.vue

async function updateArticles(){ 
  await fetch('http://localhost:3000/articles', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      "title": 'Untitled - ' + new Date().toString(),
      "author": "Andy",
      "link": "https://vuemastery.com/blog",
    })
  })
  mutateArticles()
}

(We’re using the Date object to generate some unique article titles so you can click on the button many times to add new data items with unique titles.)

The reason we await the fetch is to make sure that mutateArticles runs after everything is updated on the server.

Try clicking on the button in the browser, if you see the article list gets appended with a new item, this has been a successful experiment.


Centralized Store

At this point, you might be wondering… How does this SWR thing fit in with centralized store modules such as Vuex, Mobx, or Redux?

As you can see with our code so far, SWRV isn’t really about fetching (the fetching is still done through fetch or any fetching library you choose). Instead, it’s about caching and revalidating that cached data.

A centralized store on the client-side is technically just a cache. So tools like SWRV are meant to replace tools like Vuex. On top of serving as a centralized store, SWRV makes it easier to work with a remote API.

But what I’ve been referring to so far is about application states that are based on an API server. What about application state that isn’t based on a remote API? Local data that never needs to bother the server? In that case, we can still use Vuex alongside SWRV. But … SWRV actually allows you to work with local data, too. It doesn’t always have to be about data over HTTP.

The fetcher function can also just return a non-promise value, like string, number, or array.

📃 /src/App.vue

export default {
  name: 'App',
  components: { Count },
	setup() {
	  const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)
		
	  // NEW
	  const { data: hideList, mutate: mutateHideList } = useSWRV('hideList', () => [])

Here we created a local state hideList and initialized it with an empty array. Let’s allow the user to click on a Hide button next to each article, and then add that article’s id into this hideList array (technically a ref object that contains an array).

Create a computed property with computed:

📃 /src/App.vue

import { ref, computed } from 'vue' // CHANGE THIS
import useSWRV from 'swrv'
import Count from './components/Count.vue'

...

export default {
  name: 'App',
  components: { Count },
	setup() {
	  const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)
	  const { data: hideList, mutate: mutateHideList } = useSWRV('hideList', () => [])
		
	  // ADD THIS
	  const visibleArticles = computed(function() {
	     if(articles.value === undefined) {
	       return articles.value
	      } 
	      else {
	        return articles.value.filter(a => hideList.value.indexOf(a.id) === -1)
	       }
	    })
	...

(The conditional is for making sure that the data in article is resolved before doing the filtering.)

visibleArticles will contain the articles that are not in the hideList, so by default, visibleArticles should have the same items as articles.

Next, create a Hide button next to each article title in the template:

📃 /src/App.vue

...
<li v-for="item in visibleArticles" v-bind:key="item.id">
  <a v-bind:href="item.link" target="blank">{{ item.title }}</a>
  <button @click="hideArticle(item.id)">Hide</button>
</li>
...

When the Hide button is clicked, it will trigger the hideArticle function, passing in the article’s id.

Let’s create this function inside setup():

📃 /src/App.vue

setup() {
		...
  const visibleArticles = computed(function() {
    if(articles.value === undefined) {
      return articles.value
    } 
    else {
      return articles.value.filter(a => hideList.value.indexOf(a.id) === -1)
    }
  })

  // ADD THIS
  function hideArticle(id){
    mutateHideList(() => hideList.value.concat([id]))
  }

Finally, return visibleArticles(instead of articles) and hideArticle so they can be used in the template:

📃 /src/App.vue

setup() {
	...
	
 return {
    visibleArticles, // CHANGE
    articlesError,
    showCount,
    updateArticles,
    hideArticle, // NEW
  }
},

Now, the article-hiding feature is done, and it’s entirely local without hitting the server.

https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F5.opt.1605901836512.jpg?alt=media&token=dfad5a5b-29d4-4f87-812e-5c0d3c501c97

Although SWRV isn’t meant to be used for offline local data, it can be used this way if you really need it.


Here’s our final code:

📃 /src/App.vue

<template>
  <div>
    <p v-if="articlesError">sorry, there's been an error</p>
    <ul>
      <li v-for="item in visibleArticles" v-bind:key="item.id">
	<a v-bind:href="item.link" target="blank">{{ item.title }}</a>
        <button @click="hideArticle(item.id)">Hide</button>
      </li>
    </ul>
    <Count v-if="showCount"></Count>
    <button @click="updateArticles">Update Articles</button>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import useSWRV from 'swrv'
import Count from './components/Count.vue'
import fetcher from './fetcher'

export default {
  name: 'App',
  components: { Count },
  setup() {
    const { data: articles, error: articlesError, mutate: mutateArticles } = useSWRV('/articles', fetcher)
    const { data: hideList, mutate: mutateHideList } = useSWRV('hideList', () => [])
      
    const visibleArticles = computed(function() {
      if(articles.value === undefined) {
        return articles.value
      } 
      else {
        return articles.value.filter(a => hideList.value.indexOf(a.id) === -1)
      }
    })

    function hideArticle(id){
      mutateHideList(() => hideList.value.concat([id]))
    }

    async function updateArticles(){ 
      await fetch('/articles', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          "title": 'Untitled - ' + new Date().toString(),
          "author": "Andy",
          "link": "https://vuemastery.com/blog",
        })
      })
      mutateArticles()
    }

    const showCount = ref(false)
    setTimeout(() => showCount.value = true, 1)

    return {
      visibleArticles,
      articlesError,
      showCount,
      updateArticles,
      hideArticle,
    }
  },
}
</script>

📃 /src/components/Count.vue

<template>
  <div>
    <span>Article Count: {{ articles === undefined ? 0 : articles.length }}</span>
  </div>
</template>

<script>
import useSWRV from 'swrv'
import fetcher from './fetcher'

export default {
  name: 'Count',
  setup() {
    const { data: articles } = useSWRV('/articles', fetcher)
 
    return {
      articles
    }
  },
}
</script>

📃 /src/fetcher.js

const fetcher = function(url){
  return fetch(url).then(r => r.json())
}

export fetcher

Configurations

If you click the hide buttons one after another fast, you’ll notice that the click is sometimes ignored. That’s because the mutation is deduped with two seconds by default. Deduping means no duplicated requests can be run within a certain interval of time, and in this case, it’s two seconds. This can save server workload for fetching data from a remote server; it prevents unnecessary requests when the user is impatient while clicking on buttons repeatedly.

Now since the hideList state is local data, we don’t need any network action, hence we don’t need deduping. To make the Hide buttons work properly even when they’re clicked one after another in a split of a second, we can turn off the default two-second deduping by setting it to 0 in the third argument for useSWRV.

const { data: hideList, mutate: mutateHideList } = useSWRV('hideList', () => [], { 
	dedupingInterval: 0 
});

You can configure the behavior of SWRV using other options, too, such as revalidateOnFocus and refreshInterval.

You can set revalidateOnFocus to false if you don’t want the cache to be updated every time you jump to the browser window/tab from your other windows/tabs.

You can set refreshInterval to the number of milliseconds you want the data to get refreshed periodically. If you set it to 2000, the cache will get updated every two seconds. By default, the refreshInterval is 0, which means it’s off.

You can check out the full list of configuration options on the library’s GitHub page.


What’s next

Now that you’ve gotten the fundamentals of SWRV, it will be easy for you to pick up a similar tool in the future. SWRV is just one of many libraries that employ the stale-while-revalidate scheme. For instance, Apollo GraphQL Client can be configured to use a cache-and-network fetch policy, which is basically stale-while-revalidate under a different name. Also as a future reference, Nuxt.js’s version 3 will feature a data fetching function similar to useSWRV. So stay tuned.

Download the cheatsheets

Save time and energy with our cheat sheets.