Search code examples
vue.jsvuexvuejs3vuex-modules

Vue3//Vuex - how do I add the store only after it has initialized its modules?


I ran into a problem earlier where mapState() does not create (simple value retrieving) getters automatically for store modules. Or maybe it was a timing issue. The docs say this:

"When a component needs to make use of multiple store state properties or getters, declaring all these computed properties can get repetitive and verbose. To deal with this we can make use of the mapState helper which generates computed getter functions for us, saving us some keystrokes:"

https://vuex.vuejs.org/guide/state.html

Using mapState() in a component computed property worked great - until I moved some of that state into a module, at which point it quit working. Adding a getter to pull the state property did work but I dislike the code duplication when retrieving simple, not computed, values. If it was due to timing, is there a store.ready(app.use(store)) function somewhere?

// store.js
import { createStore } from 'vuex';
import products from './products.js'
import cart from './cart.js'

const store = createStore({
   modules:{
      products,
      cart
   },
   state() {
      return {
         isLoggedIn: false,
      }
   },
   mutations: {
      login(state) {
         state.isLoggedIn = true;
      },
      logout(state) {
         state.isLoggedIn = false;
      },
   },
})

export default store;
// products.js

export default {
   state() {
      return {
         products: [
            {
               id: 'p1',
               image:
                  'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Books_HD_%288314929977%29.jpg/640px-Books_HD_%288314929977%29.jpg',
               title: 'Book Collection',
               description:
                  'A collection of must-read books. All-time classics included!',
               price: 99.99,
            },
         ],
      }
   },
   getters:{
      products(state){
         return state.products;
      }
   }
}
<!-- productList.vue -->

<template>
  <section>
    <ul>
      <product-item
        v-for="prod in products"
        :key="prod.id"
        :id="prod.id"
        :title="prod.title"
        :image="prod.image"
        :description="prod.description"
        :price="prod.price"
      ></product-item>
    </ul>
  </section>
</template>

<script>
import ProductItem from '../components/products/ProductItem.vue';
import { mapState } from 'vuex'
//import { mapGetters } from 'vuex'

export default {
  components: {
    ProductItem,
  },
  computed: {
    ...mapState([       //This does not work
      'products'
    ])
    // ...mapGetters([    This does
    //   'products'
    // ])
  },
  mounted(){
    console.log(this.products)
  }
};
</script>

<style scoped>
  ul {
    list-style: none;
    margin: 2rem auto;
    padding: 0;
    max-width: 40rem;
  }
</style>

Solution

  • Module state is always namespaced. For example, your products state in the products module is only available under

    //         👇 module 👇 state
    store.state.products.products
    

    To use mapState with a module, you can use either of these options

    computed: {
      ...mapState({
        products: state => state.products.products
      }),
      ...mapState("products", {
        products: state.products
      })
      ...mapState("products", [
        "products"
      ])
    }
    

    Each of these will map the products state from the products module to this.products in your component.


    Your getters work because your modules are not namespaced and therefore...

    By default, actions, mutations and getters inside modules are still registered under the global namespace

    See https://vuex.vuejs.org/guide/modules.html#namespacing