raulInsto
raulInsto

Reputation: 95

Vuex: Using new store for large section of same app?

I have a store.js that I inject in my main app as follows:

new Vue({
    vuetify: new Vuetify(opts),
    store,
    el: "#app",
    },
});

My store is shaped as follows but with a lot of data in it:

store.js

export default new Vuex.Store({
   state: {},
   getters: {},
   mutations: {},
});

I have a fairly large component within the same app that I app working on now that has a lot of state to it. Is there a way I can make it's own new store or file without changing the structure of the existing store?
I was thinking if there's a way to inject 2 stores into the app, that might help but I'm not sure if that's possible.

Upvotes: 0

Views: 357

Answers (2)

Andres Foronda
Andres Foronda

Reputation: 1409

Using modules you can reduce the complexity of your applications. I use vuex in my projects this way:

Folder structure:

src
--components/ => folder
--store/ => folder
--App.vue
--main.js

Let's see the main.js file:

import Vue from 'vue';
import App from './App.vue'
// Import the index.js file from the store folder
import store from '@/store/index';

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

Now let's check the store folder structure

store/
--index.js
--moduleA/ => folder
--moduleB/ => folder

index.js file in the store folder

import Vue from 'vue';
import Vuex from 'vuex';
import moduleA from './moduleA'; // By default imports index.js file inside moduleA folder 
import moduleB from './moduleB'; // By default imports index.js file inside moduleB folder

Vue.use(Vuex);
export default new Vuex.store({
  modules: {
    moduleA,
    moduleB
  }
});

Finally, let's create our modules, moduleA folder structure(same for moduleB):

moduleA/ => folder
--index.js
--actions.js
--mutations.js
--getters.js

actions.js file:

function someAction({ commit }, payload) {
  commit("someMutation", 'newValue');
}

export default {
  someAction
}

mutations.js file:

function someMutation(state, payload) {
  state.someStateVariable = payload;
}

export default {
  someMutation
}

getters.js

export default {
  getSomeStateVariable: state => state.someStateVariable
}

And finally, the index.js file

import actions from './actions';
import getters from './getters'
import mutations from './mutations';

const state = {
  someStateVariable: 'initial value'
}

export default {
  actions,
  getters,
  mutations,
  namespaced: true, // allows to use namespace in the components
  state
}

Repeat for moduleB, and any another module if needed

And we're ready to use the store modules in our components:

<template>
  <div>
    {{ someStateVariable }}
    <button @click="setAValue">Set a new value</button>
  </div>
</template>
<script>
import { mapActions, mapGetters} from 'vuex';
export default{
  name: "AComponent",
  computed: {
    ...mapGetters({
      // You can set any name as key, but the way to access the getter is
      // with a string with the format "moduleName/getterName"
      someStateVariable: "moduleA/getSomeStateVariable"
    })
  },
  methods: {
    ...mapActions({
      // Same as mapGetters
      someAction: "moduleA/someAction"
    }),
    setAValue() {
      // Actions within the mapActions helpers, can be accessed as local
      // methods
      this.someAction("A new value for the someStateVariable")
    }
  }
}
</script>

It is a lot of info, I know, so if something is not clear, please let me know and I will explain further.

Upvotes: 0

muka.gergely
muka.gergely

Reputation: 8329

You can use Vuex modules

HOW TO ADD A MODULE TO VUEX

It's explained pretty straightforward on the page linked at the top, but a snippet may help understand it better:

// define a module
const moduleA = {
  state: () => ({
    titleText: 'This is a Vuex module/namespace snippet',
    items: [
      'item 1',
      'item 2',
      'item 3'
    ],
  }),

  mutations: {
    ADD_ITEM(state) {
      let { items } = JSON.parse(JSON.stringify(state))
      const nextItem = `item ${ items.length + 1 }`
      items.push(nextItem)
      state.items = items
    }
  },

  actions: {
    addItem({ commit }) {
      commit('ADD_ITEM')
    }
  },

  getters: {
    getItems: ({ items }) => items,
    filterItem: ({ items }) => item => {
      return items.find(e => e === item)
    }
  }
}

// define the root store, and add the module to it
const store = new Vuex.Store({
  state: {
    rootItems: [
      'root item 1',
      'root item 2'
    ]
  },
  modules: {
    moduleA: {
      namespaced: true, // setting namespaced
      ...moduleA // spreading the module
    }
  }
})

new Vue({
  el: "#app",
  store, // only the root store is added here!
  computed: {
    items() {
      // this getter is called in the namespace of the module
      return this.$store.getters['moduleA/getItems']
    },
    filteredItem() {
      // this getter is called in the namespace of the module and a parameter
      return this.$store.getters['moduleA/filterItem']('item 1')
    },
    title() {
      // this state is called in the namespace of the module
      return this.$store.state.moduleA.titleText
    },
    rootItems() {
      // this state is from the root store - no namespace
      return this.$store.state.rootItems
    },
    mergedList() {
      // you can mix-n-match the stores here (of course it could be solved inside Vuex - but that's a bit trickier :) )
      return [...this.items, ...this.rootItems]
    }
  },
  methods: {
    addItem() {
      // the action is dispatched in the namespace of the module
      this.$store.dispatch('moduleA/addItem')
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
  <div>{{ title }}</div>
  <hr />
  <div>MODULEA FILTERED ITEM:</div>
  <div>{{ filteredItem }}</div>
  <hr />
  <div>MODULEA ITEM LIST:</div>
  <div v-for="item in items" :key="item">
    {{ item }}
  </div>
  <button @click="addItem">ADD ITEM</button>
  <hr />
  <div>ROOT ITEM LIST:</div>
  <div v-for="item in rootItems" :key="item">
    {{ item }}
  </div>
  <hr />
  <div>MERGED ITEM LIST:</div>
  <div v-for="item in mergedList" :key="item">
    {{ item }}
  </div>
  <hr />
</div>

I tried to put the basic ideas in the snippet above:

  • mapping a state value from a namespace Vuex module

  • mapping a state value from the root Vuex store

  • mapping a getter with and without a parameter from a namespaced Vuex module

  • dispatching an action of a namespaced Vuex module

From this, you can see that adding a module is exactly what you are looking for: keeping the already existing store and adding another one to it.

SUGGESTION

  1. Namespacing is not required in Vuex modules but strongly advised. They may make your code a bit longer (calling things in a namespace) but keeps the logic much cleaner.
  2. The whole thing looks a bit different if you use separate files for the modules - only that case cannot be shown here on StackOverflow. You'd need to export the Vuex modules and import them in your root store (or make an automated import - one solution given here: Architecting Vuex store for large scale Vue.js applications)
  3. If you have a huge store with this method you could break it up to logical modules, or you could just separate state, mutations, actions, and getters in distinct files.

TL;DR

Add a module to your store:

// store.js
import Vue from 'vue';
import Vuex from 'vuex';
import { moduleA } from './moduleA'

Vue.use(Vuex);

const initialState = () => ({})

const state = initialState()
const mutations = {}
const actions = {}
const getters = {}

export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters,
  modules: {
    moduleA
  },
});

Create & export your module (so the root store can import it):

// moduleA.js
const initialState = () => ({})

const state = initialState()
const mutations = {}
const actions = {}
const getters = {}

export const moduleA = {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}

Upvotes: 1

Related Questions