Raphael Ugwu Writer, Software Engineer and a lifelong student.

Advanced data fetching techniques in Vue

5 min read 1400

advanced data fetching techniques in vue

When building applications with frontend frameworks, one aspect that generates a lot of opinions is the concept of data fetching. In basic scenarios, you would handle your data using a combination of an HTTP client (e.g Axios) and Vue’s lifecycle methods.

More complex situations such as larger applications with multiple components, which in most cases will be routed, will require a different approach. Also, the sequence in which data is to be fetched — before or after certain components have been loaded — is also a concern. In this post, we’ll take a look at the best practices for fetching data in advanced scenarios when working with Vue. We’ll also learn how we can use Vue 3’s composition API to leverage the stale-while-revalidate technique in data fetching.

Data fetching while navigating routed components

Data fetching when various routed components are involved could get complex. In some cases, we may want to display some data while a particular route is being navigated. Vue Router handles this properly by providing options to either fetch the data before navigation is made or after. Let’s take a look at both scenarios.

Data fetching before navigation

Vue Router provides a number of hooks to handle data fetching before navigation:

  • The beforeRouteEnter() hook which takes three arguments – to, from, and next. Like the name suggests, it is used to fetch the data needed in a component before navigation is done to that component
  • The beforeRouteUpdate() hook which also takes the same arguments as beforeRouteEnter(), is used to update the component with the data which has been fetched

We have an app with two routed components that display the current and ledger balances of our bank account. When displaying our ledger balance, we want to fetch the data before navigating to the component. First, we’ll create the script that provides the data for our ledger balance:

// LedgerBalance.js

export default (callback) => {
  const LedgerBalance = "1500 USD";
  setTimeout(() => {
    callback(null, LedgerBalance);
  }, 3000);
};

Next, we’ll create our ledger balance component:

<!-- LedgerBalance.vue -->

<template>
  <div>Hello, your ledger balance is {{ balance }}</div>
</template>
<script>
import ledgerBalance from "../scripts/LedgerBalance";
export default {
  name: "Ledger",
  data() {
    return { balance: null };
  },
  // Here the component is yet to be loaded
  beforeRouteEnter(to, from, next) {
    ledgerBalance((err, balance) => {
      next(vm => vm.setBalance(err, balance));
    });
  },
  // On calling beforeRouteUpdate, the component is loaded and the route changes
  beforeRouteUpdate(to, from, next) {
    this.balance = null;
    ledgerBalance((err, balance) => {
      this.setBalance(err, balance);
      next();
    });
  },
  methods: {
    setBalance(err, balance) {
      if (err) {
        console.error(err);
      } else {
        this.balance = balance;
      }
    }
  }
};
</script>

In LedgerBalance.vue, the beforeRouteEnter() hook ensures that data is fetched from LedgerBalance.js before the Ledger component is loaded.

Next when Ledger is loaded and the route changes, the setBalance() method within beforeRouteUpdate() is then used to set the data.

Then we’ll define the route path for our ledger balance:

// main.js

import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App";
import Ledger from "./components/LedgerBalance";
Vue.use(VueRouter);
const router = new VueRouter({
  routes: [
    { path: "/Ledger", component: Ledger }
  ]
});
new Vue({
  render: (h) => h(App),
  router
}).$mount("#app");

After defining the route path, we’ll include it in the main view:

We made a custom demo for .
No really. Click here to check it out.

<!-- App.vue -->

<template>
  <div id="app">
    <div class="nav">
      <router-link to="/Ledger">Ledger Balance</router-link>
    </div>
    <hr>
    <div class="router-view">
      <router-view></router-view>
    </div>
  </div>
</template>
<script>
export default { name: "App" };
</script>

When navigating to the Ledger component, we can observe the delay (due to the setTimeout() function in LedgerBalance.js ) as the data is fetched before navigation:

Data fetching after navigation

In some cases, we may want to fetch our data after we’ve navigated to our component. This could be useful where we are working with data that changes in real-time. Such as the current balance of an account. In this instance, we would first define the function which handles the data for our current balance:

// CurrentBalance.js

export default (callback) => {
  const CurrentBalance = "1000 USD";
  setTimeout(() => {
    callback(null, CurrentBalance);
  }, 3000);
};

Next, when creating our CurrentBalance component, we’ll use the created() lifecycle hook to call fetchBalance(), our data fetching method:

/<!-- CurrentBalance.vue -->

<template>
  <div>Hello, your current balance is {{ balance }}</div>
</template>
<script>
import currentBalance from "../scripts/CurrentBalance";
export default {
  name: "Current",
  data() {
    return { balance: null };
  },
  created() {
    this.fetchBalance();
  },
  methods: {
    fetchBalance() {
      currentBalance((err, balance) => {
        if (err) {
          console.error(err);
        } else {
          this.balance = balance;
        }
      });
    }
  }
};
</script>

Taking into account that the data will be fetched after navigation occurs, a loading component such as a progress bar or a spinner would be useful so it doesn’t look like there’s been an error:

Stale-while-revalidate in Vue

Traditionally, Vue apps used the mounted() hook to fetch data. To fetch data from an API would be something similar to the code sample below:

<template>
  <div :key="car.carmodel" v-for="car in cars">
    {{ car.carmodel }}
  </div>
</template>

<script>
export default {
  name: 'Cars',
  data() {
    return {
      cars: []
    }
  },
  mounted() {
    fetch('/api/cars')
      .then(res => res.json())
      .then(carJson => {
        this.cars = carJson
      })
  }
}
</script>

Suppose more data is required here, this could result in fetching data from multiple endpoints. This isn’t feasible as navigating between different components will result in making an API request for each navigation. These multiple API requests could slow things down and create a poor experience for users of this app:

<template>
   <div v-if="about">
     <div>{{ about.car.model }}</div>
     <div>{{ about.bio }}</div>
   </div>
   <div v-else>
     <Loading />
   </div>
 </template>

 <script>
 export default {
   name: 'Bio',
   props: {
     model: {
       type: String,
       required: true
     }
   },
   data() {
     return {
       about: null
     }
   },

   mounted() {
     fetch(`/api/car/${this.model}`)
       .then(res => res.json())
       .then(car => fetch(`/api/car/${car.id}/about`))
       .then(res => res.json())
       .then(about => {
         this.about = {
           ...about,
           car
         }
       })
   }
 }
 </script>

Ideally what’s needed is an efficient way of caching already visited components such that each component which has already been visited presents data readily, even when it is navigated away from and back. Here’s where the “stale-while-revalidate” concept is useful.

Stale-while-revalidate (swr) enables us cached, already fetched data while we fetch additional requested data. In the above code sample, every request to view the car’s bio would also re-trigger the request to view the car’s model even if that has been seen. With swr, we can cache the car’s model so when a user requests for the car’s bio, the model is seen while the data for the car’s bio is being fetched.

In Vue, the stale-while-revalidate concept is made possible through a library (swrv) which largely makes use of Vue’s composition API. Employing a more scalable approach, we can use swrv to fetch details of our car like this:

import carInfo from './carInfo'
import useSWRV from 'swrv'

export default {
  name: 'Bio',
  props: {
    model: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const { data: car, error: carError } = useSWRV(
      `/api/cars/${props.model}`,
      carInfo
    )
    const { data: about, error: aboutError } = useSWRV(
      () => `/api/cars/${car.value.id}/about`,
      carInfo
    )

    return {
      about
    }
  }
}

In the code sample above, the first useSWRV hook uses the API’s URL as a unique identifier for each request made and also accepts a carInfo function. carInfo is an asynchronous function which is used to fetch details about the car, it also uses the API’s URL as a unique identifier.

The second useSWRV hook watches for changes from the first hook and revalidates its data based on these changes. The data and error values are respectively populated for successful and failed responses to requests made by the carInfo function.

Summary

Concepts to fetching data while navigating routed components and caching already fetched data are awesome ways to create an impressive user experience in your application if used properly. To get a deeper dive into stale-while-revalidate, I recommend you take a look at Markus Oberlehner’s post on the topic.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - .

Raphael Ugwu Writer, Software Engineer and a lifelong student.

Leave a Reply