When building apps that rely on data from an API, two things are essential: The stale-while-revalidate caching pattern helps us to strike a balance between both. we want our data to be fresh, and we want it fast. If our cache already contains a copy of the requested information, But at the same time, This can make navigating our apps feel instantaneously while also making sure that the user sees the latest data eventually. we can immediately show the (potentially stale) data. we revalidate our cache and fetch a new version. , you can see two components which both fetch their data from the same API endpoint. Even if a new component is added, In this video Thanks to the SWR composable, the data is always in sync. it immediately has access to the cached data while fetching a fresh copy in the background. If you want to try it yourself, you can check out the demo hosted on and . Netlify take a closer look at the code on GitHub Inspiration in the React world In the last couple of weeks, I played around with Preact, and I learned a thing or two about how to do things in the React ecosystem. I discovered that because of how React Hooks work, there are a few special requirements on how to fetch data in React components. This is why since the introduction of Hooks, two quite similar data fetching libraries saw the light of day: and . What we build today is inspired by those libraries. React Query SWR SWR Data Fetching Composable with Vue 3 I kept an eye on the development of the Vue 3 Composition API since the first RFC was published, but I never got around actually using it extensively. After experimenting with it for a couple of days, I can say this: it is fantastic! Now that I have (minimal) knowledge about how things are done in React, I can even more appreciate how powerful and straightforward to use the Composition API is. So without further ado, let’s take a look at a naive implementation of a stale-while-revalidate data fetching composable for Vue 3. { reactive, readonly, toRefs } ; LRU ; { asArray } ; CACHE = LRU({ : }); DEFAULT_OPTIONS = { : , }; STATE = { : ( ), : ( ), : ( ), : ( ), }; { options = { ...DEFAULT_OPTIONS, ...customOptions, }; parameters = asArray(parameter); cacheKey = ; cacheKeyDedupe = ; cachedResponse = CACHE.get(cacheKey); response = cachedResponse || reactive({ : , : , : , : , }); (!cachedResponse) CACHE.set(cacheKey, response); load = () => { { (CACHE.get(cacheKeyDedupe)) ; CACHE.set(cacheKeyDedupe, , options.dedupingInterval); response.state = response.data ? STATE.revalidating : STATE.loading; response.data = .freeze( callback(...parameters)); response.state = STATE.idle; } (error) { .error(error); CACHE.del(cacheKeyDedupe); response.error = error; response.state = STATE.error; } }; response.reload = load; load(); toRefs(readonly(response)); } // src/composables/swr-cache.js import from 'vue' import from 'lru-cache' import from '../utils/as-array' const new max 1024 const dedupingInterval 2000 // We use `Symbol` for the state properties to prevent // consumers of this package to use raw strings. // See: https://bit.ly/2Lh2lEM export const error Symbol 'error' idle Symbol 'idle' loading Symbol 'loading' revalidating Symbol 'revalidating' export ( ) function useSwrCache parameter, callback, customOptions const // Wrap `parameter` in an array if it is not an array already. const // Naive way of creating a unique cache key. const ` ` ${ .stringify(parameters)} JSON ${callback.toString()} const ` _dedupe` ${cacheKey} const // Use the reactive object from the cache or create a new one. const data null error null reload undefined state undefined if const async try // Dedupe requests during the given interval. if return true // Wait for the result of the callback and set // the reactive `data` property. Object await catch console // Using `toRefs()` makes it possible to use // spreading in the consuming component. // Making the return value `readonly()` prevents // users from mutating global state. return One beautiful thing about this approach but also a potential gotcha is that every instance that resolves to the same cache key shares the same reactive object. So if one component triggers a reload, the data is updated everywhere. It almost works like a global Vuex store. One thing to mention is that the way how the is created is not very reliable. cacheKey Because: . JSON.stringify({ a: 'a', b: 'b' }) !== JSON.stringify({ b: 'b', a: 'a' }) Property order does matter! But as long as you only use strings as parameters, that’s no problem. Usage Now let’s take a look at how we can use our newly created composable. useSwrCache() Profile Name: {{ user.name }} LOADING ... {{ error }} REVALIDATING ... < > template < > div < > h2 </ > h2 < = > template v-if "user" <!-- ... --> </ > template < = > template v-else-if "state === STATE.loading" </ > template < = > template v-else-if "state === STATE.error" </ > template <!-- Stale data is shown while revalidating! --> < = > template v-if "state === STATE.revalidating" < > small </ > small </ > template </ > div </ > template < > script { fetcher } ; { useSwrCache, STATE } ; { : , setup() { { : user, error, state, } = useSwrCache( , fetcher); { STATE, error, state, user, }; }, }; import from '../utils/fetcher' import from '../composables/swr-cache' export default name 'UserProfile' const data 'https://jsonplaceholder.typicode.com/users/1' return </ > script The utility is a simple wrapper around . returns a reactive object with the , the current and, if applicable, an . fetcher fetch() useSwrCache data state error We have two different states for (no cached data) and . loading revalidating Wrapping it up Although it works perfectly fine, this is a quite raw implementation and in no way as comprehensive as React Query or SWR. If you consider using something like this in production, you might want to use a more bulletproof approach for generating unique cache keys. Previously published at https://markus.oberlehner.net/blog/stale-while-revalidate-data-fetching-composable-with-vue-3-composition-api/