Search code examples
javascriptvue.jsvuejs2vuex

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


Solution

  • 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 }) {