How to use async/await in vue lifecycle hooks with vuex?
Asked Answered
F

3

8

When I dispatch an action in App.vue component in mounted() lifecycle hook, it runs after other components load. I am using async/await in my action and mounted lifecycle hook.

App.vue file

methods: {
   ...mapActions({
      setUsers: "setUsers",
   }),
},
async mounted() {
        try {
            await this.setUsers();
        } catch (error) {
            if (error) {
                console.log(error);
            }
        }
    },

action.js file:

async setUsers(context) {
        try {
            const response = await axios.get('/get-users');
 
            console.log('setting users');
 
            if (response.data.success) {
                context.commit('setUsers', {
                    data: response.data.data,
                });
            }
        } catch (error) {
            if (error) {
                throw error;
            }
        }
    },

In Users list component, I need to get users from vuex. So I am using mapGetters to get Users list.

...mapGetters({
            getUsers: "getUsers",
        }),
    mounted() {
            console.log(this.getUsers);
    },

But the problem is "setting users" console log in running after console logging the this.getUsers.

In Users list component, I can use getUsers in the template but when I try to console log this.getUsers it gives nothing.

How can I run app.vue file before running any other components?

Fellatio answered 9/1, 2021 at 5:40 Comment(0)
A
19

You are using async await correctly in your components. It's important to understand that async await does not hold off the execution of your component, and your component will still render and go through the different lifecycle hooks such as mounted.

What async await does is hold off the execution of the current context, if you're using it inside a function, the code after the await will happen after the promise resolves, and in your case you're using it in the created lifecycle hook, which means that the code inside the mounted lifecycle hook which is a function, will get resolved after the await.

So what you want to do, is to make sure you render a component only when data is received.

Here's how to do it:

  1. If the component is a child component of the parent, you can use v-if, then when the data comes set data to true, like this:
<SomeComponent v-if="hasData" />
data() {
  return {
    hasData: false,
  }
}

async mounted() {
  const users = await fetchUsers()
  this.hasData = true;
}
  1. If the component is not a child of the parent, you can use a watcher to let you know when the component has rendered. When using watch you can to be careful because it will happen every time a change happens.

A simple rule of thumb is to use watch with variables that don't change often, if the data you're getting is mostly read only you can use the data, if not you can add a property to Vuex such as loadingUsers.

Here's an example of how to do this:

<SomeComponent v-if="hasData" />
data: {
  return {
    hasData: false,
  }
},
 
computed: {
  isLoading() {
    return this.$store.state.app.users;
  }
},
  
watch: {
  isLoading(isLoading) {
    if (!isLoading) {
      this.hasData = true;
    }
  }
}
Astomatous answered 9/1, 2021 at 6:4 Comment(0)
M
0

Question: Do you expect to run await this.setUsers(); every time when the app is loaded (no matter which page/component is being shown)?

If so, then your App.vue is fine. And in your 'Users list component' it's also fine to use mapGetters to get the values (note it should be in computed). The problem is that you should 'wait' for the setUsers action to complete first, so that your getUsers in the component can have value.

A easy way to fix this is using Conditional Rendering and only renders component when getUsers is defined. Possibly you can add a v-if to your parent component of 'Users list component' and only loads it when v-if="getUsers" is true. Then your mounted logic would also work fine (as the data is already there).

Moniz answered 9/1, 2021 at 5:56 Comment(0)
O
0

if you're fetching a data from an API, then it is better to dispatch the action inside of created where the DOM is not yet rendered but you can still use "this" instead of mounted. Here is an example if you're working with Vuex modules:

created() {
  this.fetchUsers();
},
methods: {
  async fetchUsers() {
    await this.$store.dispatch('user/setUsers');
  },
},
computed: {
  usersGetters() {
    // getters here
  },
},
Obligation answered 25/1, 2021 at 5:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.