This was the question got me stuck for a little bit. Unfortunately, I coudn't find answer here (asking also didn't help). So after doing some research and asking here and there, it seems that I got the solution to this issue.
If you have a question that you already know the answer to, and you would like to document that knowledge in public so that others (including yourself) can find it later.
Of course, my answer may not be the ideal one, moreover I know it is not, that's the key point why I'm posting - to improve it.
Note, I'm not using actions in example. The idea is the same.
Let's begin with stating the problem:
Imagine we have App.vue
which dynamically generates its local component named Hello
.
<template>
<div id="app">
<div>
<hello v-for="i in jobs" :key="i" :id="i"></hello>
<button @click="addJob">New</button>
</div>
</div>
</template>
<script>
import Hello from './components/Hello'
export default {
components: {
Hello
}...
store.js
export const store = new Vuex.Store({
state: {
jobs: []
}
})
We are using v-for
directive to generate components by iterating through an array jobs
. Our store
as of now consists of only state
with an empty array.
Button New
should do 2 things:
1) create new component Hello
, in other words add element to jobs
(let it be numbers), which are going to be assigned as key
and id
of <hello>
, and passed to local component as props
.
2) generate local stores - modules - to keep any data scoped to newly created components.
Hello.vue
<template>
<div>
<input type="number" :value="count">
<button @click="updateCountPlus">+1</button>
</div>
</template>
export default {
props: ['id']
}
Simple component - input with a button adding 1.
Our goal is to design something like this:
For the first operation of NEW
button - generating components - we add mutation
to our store.js
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
...
}
Second, creating local modules. Here we're going to use reusableModule
to generated multiple instances of a module. That module we keep in separate file for convinience. Also, note use of function for declaring module state.
const state = () => {
return {
count: 0
}
}
const getters = {
count: (state) => state.count
}
const mutations = {
updateCountPlus (state) {
state.count++
}
}
export default {
state,
getters,
mutations
}
To use reusableModule
we import it and apply dynamic module registration.
store.js
import module from './reusableModule'
const {state: stateModule, getters, mutations} = module
export const store = new Vuex.Store({
state: {
jobs: []
},
mutations: {
addJob (state) {
state.jobs.push(state.jobs.length + 1)
store.registerModule(`module${state.jobs.length}`, {
state: stateModule,
getters,
mutations,
namespaced: true // making our module reusable
})
}
}
})
After, we're going to link Hello.vue
with its storage. We may need state
, getters
, mutations
, actions
from vuex
. To access storage we need to create our getters
. Same with mutations
.
Home.vue
<script>
export default {
props: ['id'],
computed: {
count () {
return this.$store.getters[`module${this.id}/count`]
}
},
methods: {
updateCountPlus () {
this.$store.commit(`module${this.id}/updateCountPlus`)
}
}
}
</script>
Imagine we have lots of getters
, mutations
and actions
. Why not use {mapGetters}
or {mapMutations}
? When we have several modules and we know the path to module needed, we can do it. Unfortunately, we do not have access to module name.
The code is run when the component's module is executed (when your app is booting), not when the component is created. So these helpers can only be used if you know the module name ahead of time.
There is little help here. We can separate our getters
and mutations
and then import them as an object and keep it clean.
<script>
import computed from '../store/moduleGetters'
import methods from '../store/moduleMutations'
export default {
props: ['id'],
computed,
methods
}
</script>
Returning to App
component. We have to commit our mutation
and also let's create some getter
for App
. To show how can we access data located into modules.
store.js
export const store = new Vuex.Store({
state: {
jobs: []
},
getters: {
jobs: state => state.jobs,
sumAll (state, getters) {
let s = 0
for (let i = 1; i <= state.jobs.length; i++) {
s += getters[`module${i}/count`]
}
return s
}
}
...
Finishing code in App
component
<script>
import Hello from './components/Hello'
import {mapMutations, mapGetters} from 'vuex'
export default {
components: {
Hello
},
computed: {
...mapGetters([
'jobs',
'sumAll'
])
},
methods: {
...mapMutations([
'addJob'
])
}
}
</script>
Hi and thank you for posting your question and your solution.
I started learning Vuex couple days ago and came across a similar problem. I've checked your solution and came up with mine which doesn't require registering new modules. I find it to be quite an overkill and to be honest I don't understand why you do it. There is always a possibility I've misunderstood the problem.
I've created a copy of your markup with a few differences for clarity and demonstration purposes.
I've got:
JobList.vue (which is responsible for wrapping the job(s) list items)
<template>
<div>
<job v-for="(job, index) in jobs" :data="job" :key="job.id"></job>
<h3>Create New Job</h3>
<form @submit.prevent="addJob">
<input type="text" v-model="newJobName" required>
<button type="submit">Add Job</button>
</form>
</div>
</template>
<script>
import store from '../store/index'
import job from './job';
export default {
components: { job },
data() {
return {
newJobName: ''
};
},
computed: {
jobs() {
return store.state.jobs.jobs;
}
},
methods: {
addJob() {
store.dispatch('newJob', this.newJobName);
}
}
}
</script>
The Job
<template>
<div>
<h5>Id: {{ data.id }}</h5>
<h4>{{ data.name }}</h4>
<p>{{ data.active}}</p>
<button type="button" @click="toggleJobState">Toggle</button>
<hr>
</div>
</template>
<script>
import store from '../store/index'
export default {
props: ['data'],
methods: {
toggleJobState() {
store.dispatch('toggleJobState', this.data.id);
}
}
}
</script>
And finally the jobs.js Vuex module file:
export default {
state: {
jobs: [
{
id: 1,
name: 'light',
active: false
},
{
id: 2,
name: 'medium',
active: false
},
{
id: 3,
name: 'heavy',
active: false
}
]
},
actions: { //methods
newJob(context, jobName) {
context.state.jobs.push({
id: context.getters.newJobId,
name: jobName,
active: false
});
},
toggleJobState(context, id) {
context.state.jobs.forEach((job) => {
if(job.id === id) { job.active = !job.active; }
})
}
},
getters: { //computed properties
newJobId(state) { return state.jobs.length + 1; }
}
}
It's possible to add new jobs to the store and as the "active" property suggest, you can control every single individual job without the need for a new custom vuex module.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With