Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ensure that Vuex state is loaded before render component

I have a add-user.vue component. It is my template for adding a new user and editing a existing user. So on page load I check if route has a id, if so I load a user from a state array to edit it. My issue is that user is undefined because the state array users is empty. How can I ensure that my user object isn't undefined. It does load sometimes but on refresh it doesn't. I thought I had it covered but nope. This is my setup. What am I missing here?

Store

state: {
  users: []
},

getters: {
  users: state =>
    _.keyBy(state.users.filter(user => user.deleted === false), 'id')
},

actions: {
  fetchUsers({
    commit
  }) {
    axios
      .get('http://localhost:3000/users')
      .then(response => {
        commit('setUsers', response.data);
      })
      .catch(error => {
        console.log('error:', error);
      });
  }
}

In my add-user.vue component I have the following in the data() computed:{} and created()

data() {
  return {
    user: {
      id: undefined,
      name: undefined,
      gender: undefined
    }
  };
},

computed: {
  ...mapGetters(['users'])
},

created() {
  if (this.$route.params.userId === undefined) {
    this.user.id = uuid();
    ...
  } else {
    this.user = this.users[this.$route.params.userId];
  }
}

Template

<template>
  <div class="add-user" v-if="user"></div>
</template>

My User.vue I have the following setup, where I init the fetch of users on created()

<template>
  <main class="main">
    <AddUser/>
    <UserList/>
  </main>
</template>

<script>
import AddUser from '@/components/add-user.vue';
import UserList from '@/components/user-list.vue';

export default {
    name: 'User',

    components: {
        AddUser,
        UserList
    },

    created() {
        this.$store.dispatch('fetchUsers');
    }
};
</script>

I have tried this. When saving in my editor it works but not on refresh. The dispatch().then() run before the mutation setting the users does.

created() {
  if (this.$route.params.userId === undefined) {
    this.user.id = uuid();
    ...
  } else {
    if (this.users.length > 0) {
                this.user = this.users[this.$route.params.userId];
            } else {
                this.$store.dispatch('fetchUsers').then(() => {
                    this.user = this.users[this.$route.params.userId];
                });
            }
  }
}
like image 674
Dejan.S Avatar asked Aug 13 '18 08:08

Dejan.S


People also ask

Is Vuex getter cached?

Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. As of Vue 3.0, the getter's result is not cached as the computed property does.

What is the use of Mapstate in Vuex?

Mapping in Vuex enables you to bind any of the state's properties, like getters, mutations, actions, or state, to a computed property in a component and use data directly from the state. Although we can get the job done with this. $store.state.user.data.name , we can use a map helper to simplify it to this.

Should I use pinia or Vuex?

Compared to Vuex, Pinia provides a simpler API with less ceremony, offers Composition-API-style APIs, and most importantly, has solid type inference support when used with TypeScript.

What is difference between mutation and action in Vuex?

Mutations are intended to receive input only via their payload and to not produce side effects elsewhere. While actions get a full context to work with, mutations only have the state and the payload .


3 Answers

I would use beforeRouteEnter in User.vue so that the component is not initialized before the data is loaded. (Assuming you are using vue-router)

beforeRouteEnter (to, from, next) {
    if (store.state.users.length === 0) {
        store.dispatch(fetchUsers)
        .then(next);
    }
},

You'll need to import store from 'path/to/your/store' because this.$store is not available until the component is initialized.

like image 198
Shu Yoshioka Avatar answered Oct 01 '22 11:10

Shu Yoshioka


Although this solved, but I will answer for future comers. You may issue the dispatch beforehand as Shu suggested, or you may still dispatch on same component mounted hook, but use some state variable to track the progress:

data:{
    ...
    loading:false,
    ...
},
...
mounted(){
this.loading = true,
this.$store
  .dispatch('fetchUsers')
  .finally(() => (this.loading=false));
}

Then in your template you use this loading state variable to either render the page or to render some spinner or progress bar:

<template>
    <div class='main' v-if="!loading">
        ...all old template goes her
    </div>
    <div class="overlay" v-else>
        Loading...
    </div>
</template>

<style scoped>
    .overlay {
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 10;
      color: #FFFFFF;
    }
</style>
like image 30
adnanmuttaleb Avatar answered Oct 01 '22 12:10

adnanmuttaleb


For this particular case it was just going back and forth to the same vue instance. Solved it by adding :key="some-unique-key", so it looks like this.

<template>
  <main class="main">

    <AddUser :key="$route.params.userId"/>

    <UserList/>

  </main>
</template>
like image 23
Dejan.S Avatar answered Oct 01 '22 11:10

Dejan.S