I've been wrapping my head around this one and I can't quite figure out the way to do this.
How can I have input data-binding to a store object property? Meaning: my component only knows the store object, but it does not know in advance which properties the object contains.
Take this example:
Vue.component('os-choice', {
template: `
<div>
<h5>Select OS</h5>
<div>
<label class="checkbox">
<input type="checkbox" value="linux" v-model="linux">
Linux
</label>
</div>
<div>
<label class="checkbox">
<input type="checkbox" value="macos" v-model="macos">
MacOS
</label>
</div>
<div>
<label class="checkbox">
<input type="checkbox" value="windows" v-model="windows">
Windows
</label>
</div>
</div>
`,
computed: {
linux: {
get() {
return store.state.linux;
},
set(val) {
store.commit('updateLinux', {
linux: val
});
}
},
macos: {
get() {
return store.state.macos;
},
set(val) {
store.commit('updateMacos', {
macos: val
});
}
},
windows: {
get() {
return store.state.windows;
},
set(val) {
store.commit('updateWindows', {
windows: val
});
}
},
}
});
Vue.component('os-choice2', {
template: `
<div>
<h5>Select OS</h5>
<div v-for="(value,index) in os" :key="index">
<label class="checkbox">
<input type="checkbox" value="index" v-model="value">
{{index}}
</label>
</div>
</div>
</div>
`,
computed: {
os: {
get(){ return store.state.os; },
set(event){
var name = event.target.value;
var value = event.target.checked;
store.commit('updateOs', {name: name, value: value});
}
},
}
});
const store = new Vuex.Store({
state: {
// initial state
linux: true,
macos: true,
windows: false,
os: {
linux: true,
macos: true,
windows: false,
}
},
mutations: {
updateLinux(state, payload) {
console.log('STORE: updated linux');
state.linux = payload.linux;
},
updateMacos(state, payload) {
console.log('STORE: updated MacOS');
state.macos = payload.macos;
},
updateWindows(state, payload) {
console.log('STORE: updated Windows');
state.windows = payload.windows;
},
updateOs(state, payload) {
console.log('STORE: updated OS');
state.os[payload.name] = payload.value;
}
}
});
var app = new Vue({
el: "#app",
})
.row{
width: 100%;
}
.column{
width: 50%;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
<div id="app" style="text-align:center;">
<div class="row">
<div class="column">
<os-choice></os-choice>
</div>
<div class="column">
<os-choice2></os-choice2>
<div>
</div>
</div>
The first component works great. The second component does not update the store: it generates error "[vuex] Do not mutate vuex store state outside mutation handlers."
I believe I understand why it is happening: Since the computed property of the component points to the object (and not the property being changed), v-model will try to change the deep property, thus forcing a change on the store which will throw an error before the component even calls the set() method on the object, right?
So what I would like to know is how would be the proper way to go about this?
My real-world scenario is that I'm building a dynamic filter with checkboxes and at runtime I do not know how many options it will have. This means I'll loop through my options to create the checkboxes, but then the data-binding would have to be in the form of "values[]". And this is where my headaches start.
Thank you all in advance
From VueJs documentation:
Custom events can also be used to create custom inputs that work with v-model. Remember that:
<input v-model="searchText">
does the same thing as:
<input v-bind:value="searchText" v-on:input="searchText =
$event.target.value"
>
When used on a component, v-model instead does this:
<custom-input v-bind:value="searchText" v-on:input="searchText =
$event"
></custom-input>
So, instead of using v-model
, you could use value
as the v-bind:value="value"
and map the event to update the store appropriately, something like: v-on:input="store.commit('store.commit('updateOs', event.target.value)"
The other component works since you are following exactly what Vuex recommend doing: https://vuex.vuejs.org/guide/forms.html
But on the v-for
, you are using the store model directly.