9
votes

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];
                });
            }
  }
}
3
it's likely that the computed value is not yet mapped in created hook. try using this.$store.getters.users in created or move ur codes into mounted hookJacob Goh
@JacobGoh from my understanding having the getters in created makes no difference, as it is reactive. When the fetchUsers is done the getters will have value.Dejan.S
I would suggest that you always anticipate that the user is not loaded - so first check if it is in the store. If yes - then simply use it. Otherwise dispatch an AJAX request to load it.IVO GELOV

3 Answers

10
votes

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.

5
votes

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>
1
votes

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>