Redzwan Latif
Redzwan Latif

Reputation: 886

Vuex - getting state after mutation

I am very new to State Management. I'm currently building a product list with infinite loading features. These are my codes:

This is my component:

<template>
  <div class="">
    <section class="space--sm">
      <div class="container">
          <div class="row">
              <div class="col-sm-12">
                    <div v-for="(data, index) in products" v-bind:key="data.id" class="item col-sm-4 col-xs-12" :id="'data-id-'+data.id" >
                        <a href="#" v-on:click.prevent="selectProduct(data)" >
                            <h4>{{data.name}}</h4>
                        </a>
                    </div>
                    <infinite-loading force-use-infinite-wrapper="true" @infinite="infiniteHandler" ref="infiniteLoading">
                        <span slot="no-results">
                            No results
                        </span>
                        <span slot="no-more">
                            There are no more results
                        </span>
                    </infinite-loading>
                </div>
          </div>
      </div>
    </section>
  </div>
</template>

<script>
import InfiniteLoading from 'vue-infinite-loading';
import { mapState, mapActions } from 'vuex';
export default {
    name: 'List',
    computed: mapState({
        products: state => state.list.products
    }),
    methods: {
        ...mapActions('list', [
            'selectProduct'
        ]),
        infiniteHandler($state) {
            setTimeout(() => {
                this.$store.dispatch('products/fetch')

                console.log(this.products.length); 
                //returns 0 but vue debugger says products state got datas

                if (this.products.length) {
                    $state.loaded();
                    if(this.products.length < 15){
                        $state.complete();
                    }
                } else {
                    $state.complete();
                }
            }, 1000);
        },
    },
    components: {
        InfiniteLoading
    }
}
</script>

This is my store:

import axios from 'axios';

// initial state
const state = {
    products: [],
    selected_product: [],
    page: 1
}

// getters
const getters = {}

// mutations
const mutations = {
    FETCH(state, products) {
        state.products = state.products.concat(products);
    },
    selectProduct (state, { product }) {
        state.selected_product = product;
    },
    updatePage (state) {
        state.page += 1;
    },
}

// actions
const actions = {
    fetch({ commit }) {
        return axios.get('/api/get/products', {
            params: {
                page: state.page
            }
        })
        .then((response) => {
            commit('updatePage')
            commit('FETCH', response.data.data)
        })
        .catch();
    },
    selectProduct ({ state, commit }, product) {
        commit('selectProduct', { product })
    }
}

export default {
    namespaced: true,
    state,
    getters,
    actions,
    mutations
}

In the infiniteHandler method, I did this :

this.$store.dispatch('products/fetch')

Afer mutation products state should have loaded data inside (checked using vue debugger) but when I do this after dispatching :-

console.log(this.products.length);

I get 0 which also means no datas present inside the state. Am I doing anything wrong? Also is this the best practice of using Vuex? Thank you

Upvotes: 1

Views: 2482

Answers (3)

ittus
ittus

Reputation: 22403

this.$store.dispatch('products/fetch') is asynchronous. console.log(this.products.length); is called before action is finished.

You should use watch in this case.

   watch: {
      products: function (newVal) {
        if (newVal.length) {
          $state.loaded();
          if(newVal.length < 15){
            $state.complete();
          }
        } else {
          $state.complete();
        }
      }
    }

Upvotes: 1

fibonacci
fibonacci

Reputation: 1

async infiniteHandler($state) { 
  setTimeout(() => {
    await this.$store.dispatch('products/fetch') 

    console.log(this.products.length); 

    if (this.products.length) {
      setTimeout(() => {
             $state.loaded();
      }, 1000);
      if(this.products.length < 15){
        $state.complete();
      }
    } else {
      $state.complete();
    }
  }, 1000);
},

Upvotes: 0

Phil
Phil

Reputation: 164970

If an asynchronous action returns a promise (as yours does), it is returned by the call to dispatch so your consumer can wait for the action to complete.

For example

async infiniteHandler($state) { // note, this is an "async" function
  setTimeout(() => {
    await this.$store.dispatch('products/fetch') // note the "await"

    console.log(this.products.length); 

    if (this.products.length) {
      $state.loaded();
      if(this.products.length < 15){
        $state.complete();
      }
    } else {
      $state.complete();
    }
  }, 1000);
},

One more thing, your catch() at the end of the fetch action won't do you any favours. I'd advise removing it so your consumers can be made aware of any errors. That, or handle the error in your action but still pass the promise rejection on to your component, eg

.catch(res => {
  // handle error, maybe with your state
  throw res // make sure to keep rejecting the promise
})

and in your component

try {
  await this.$store.dispatch('products/fetch') 
  // etc
} catch (e) {
  // do something else
}

Just noticed another thing... you should use the passed in context state in your actions, not some global state object, ie

fetch({ state, commit }) {

Upvotes: 1

Related Questions