Use Vuex to avoid multiple requests from different components

JavaScript is asynchronous, it’s big advantage, but in some cases, also a big problem. The most popular mistakes relate to AJAX requests. Many people trying to do something before there is a response with data, and it cannot work. Now we can use many comfortable tools, and don’t have to remember about that – one of them is async/await. But sometimes, it can be still a problem. Let’s say, we use Vue, have one page with multiple similar components. Each of them needs some data from the same API endpoint, and calls request. In that case, we can’t use async/await, because there a lot of completely separate components. How can we avoid multiple requests then? There are few solutions, one of them is to use Vuex and this post is about that. 

Why don’t use shared property?

Someone can say: wait a moment, you have page, you use a lot of components on it, and every need the same data. Why don’t you set this data as a prop from your parent component? Yes, can I do this, but it also means, that components are much more dependent on each other. It isn’t good practice and we should avoid that, if it’s possible. Each component – as far as possible – should be separate, should have only props for settings and core data. Also, in that case, I prefer to use Vuex to store my data, because it can be used by many, many other components.  

Real usage example: we have users list and we want storing this list in Vuex. Many components on our page can use this list. But before first usage, we must load this list from backend, from our API. If list is used by only one component, it isn’t any issue. We can do this for example using rendered flag or use computed property from store to check, if we can render our component. There is an example with rendered flag: 

    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
        // component will be visible after loadUsers
        this.rendered = true
    }

    // COMPONENT
    <template>
        <div v-if="rendered">
            (..)

And also, with computed data from the store – there is no call dispatching, because it can be called from other place, other component and we only want wait for data: 

    computed: {
        // Default state for users list should be false or null!
        ...mapState('users', {
            usersList: state => state.list,
        })

    // COMPONENT
    <template>
        <div v-if="users && users.length">
            (..)

We still need other solution

Both solutions are ok, also, both solutions don’t require any other changes… But what can guarantee us, that list will be ready? In the first case, we dispatch store from our component and we know that, it’s ok. But, if we will use many similar components, every of them will also call this dispatch and every will make request to API – it’s bad option. In second case, we don’t know anything about request. Maybe other component, or layout will dispatch for data… or maybe not? It’s also bad option.  

    // Component 1 - it will call request
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    // Component 2 - it will call second request
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    // Component 3 - it will call next request...
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    (...)

The solution is to use first option, but with blocking other requests. How can we achieve that? There is await/async, but if we will call this from other component, code will be separate and also, we don’t know anything about other callings.

Solution: store promise in Vuex!

Solutions is very simple – we can store promise from request in store, and return it, if it’s present. In that case, every other calls will wait for this one promise, for a ready list and will use the same list, when it will be ready. First thing, we must prepare field for this promise in our store state: 

state () {
    return {
        list: [],
        promise: null

And after that, we can modify our action to load list:

async loadUsers ({ state, commit }) {
  if (state.users.length) {
   return state.users
  }  

  if (state.fetchPromise) {
    return state.fetchPromise
  }

  let promise = fetch(usersUrl).then((response) => {
    commit('SET_USERS', response.data)
  })

  commit('SET_PROMISE', promise);
  return promise
}

After this change, code from example above will work in other way:

    // Component 1 - it will call request
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    // Component 2 - it will WAIT for first call!
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    // Component 3 - it will WAIT for first call!
    async beforeMount () {
        await this.$store.dispatch('users/loadUsers')
    }

    (...)

Conclusion

As you can see, we solved a complex problem using a very simple trick – using Vuex to make promise “global” for all modules, that can call the same request. In effect, we will call this dispatch and fetching data from API only once. Number of components with the same code doesn’t matter: only first will call fetching, others will wait for the same result and ready list. Now we can put dispatching data in every place, when we want to have guarantee, that data will be loaded. Without risk, that will loaded never, or will cause multiple requests to the same endpoint.  

11 thoughts on “Use Vuex to avoid multiple requests from different components

  1. Hello Lucasz, thanks for sharing your knowledge. My name is Adriano, I’m from Brazil. I have exactly the situation described above by you (same components repeated making the same requests at the same time). However, I was still new to VUE JS and VUEX and I was unable to implement their solution. Could you elaborate a little more on the steps in the tutorial above? Thanks (sorry about my English).

  2. So, I think your script works for different components. In my case, I render the same component multiple times on the same page. If I’m wrong, I’m sorry.

    1. No, it can work also if you use it in the same component rendered many times on the same page – all components must use the same vuex, it’s the key. Only first one will set promise, rest will wait for respond and data.

  3. Hi Łukasz, what a nice solution! I’m facing a similar problem and storing a promise in the store can also solve it.

    In my case I have 2 sets of data, let’s say a basicData and a detailedData. Some components use only the basicData, and so dispatch an action to fetch it. Other components use the detailedData and also dispatch an action to get it as well. The trick is that detailedData is constructed from basicData + fetched details, so its action depends on the basicData being available. Using your idea, I can now store the promise of basicData in the store and then check for it in the detailedData action (and if it’s not there, just dispatch basicData normally and wait for it).

    Thanks a lot!

  4. Really nice solution. One question, should we not clear promise from state once resolved/rejected?

    1. Hey Shashwat – yes, if you want to allow call the same request again in future, you should clear promise from state. If not, it’s not necessary.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.