How to create getters and setters for vuex namespaced module state - vuejs2

If I have a Vuex module which is namespaced, how to create the getters and setters for the states in that module when using these state in Vue components?
// My component
new Vue({
computed: {
// How do I add setters also below????
...mapState('nameSpacedModA', {
a : state => state.a,
// ...
},
// Following will only add getters..
// How to add setter ???
...mapGetters('nameSpacedModA', {
a: 'a',
b: 'b' //, ...
}
}
I am binding 'a' to a text input on a form using v-model and then when I edit the control value, Vue is giving error:
[Vue warn]: Computed property "a" was assigned to but it has no
setter.
How to solve this?

If you want do do 2 ways binding, you need to defined both getter and setter in computed properties. (Don't forget to define mutation updateA)
<input v-model="a">
// ...
computed: {
a: {
get () {
return this.$store.state.a
},
set (value) {
this.$store.commit('updateA', value)
}
}
}
Another option is using mapFields

I found another way using Vuex mapStates and mapActions helpers.
This is slightly more verbose. So using the v-model binding approach is more better.
// BTW: If you use the approach as suggested by ittus then you will use the v-model binding like below:
<input v-model="a" />
// Using the other approach that I used you will have to do two-way binding as below:
<input :value="a" #input="updateA" />
If you want to use the v-model binding then the code will be something like below:
// Vuex store
....
modules: {ModuleA, ...}
// ModuleA of store
export default {
namespaced: true,
states: {
a: '',
},
mutations: {
updateA: (state, value) => state.a = value
},
actions: {
updateA(context, value) { context.commit('updateA', value) }
}
}
// Then in your Component you will bind this Vuex module state as below
new Vue({
store,
computed: {
a: {
get() { this.$store.state.ModuleA.a; }
set(value) {this.updateA(value);}
},
},
methods: {
...mapActions('MyModule', [ updateA ]),
}
})

Related

Computed properties and Vuex

I'm a little confused on how computed properties work with Vuex. I'm using a computed getter:
var selectDisplayValues = computed({
get() {
return store.getters['expense/getSelectDisplayValues'];
}
});
When the store data changes the computed prop also changes. So far so clear.
When now assigning a new value to the computed property - the value inside the store also changes. Not just the local value of the property. Why is that so? Won't I need a setter inside the computed prop to do so?
EDIT:
I'm assigning the new values like this.
selectDisplayValues.value[`inputData[${props.index}][${props.attribute}]`] = {placeholder_value: "Bitte wählen...", value: "", reassigned: false};
Also I'm using a v-model on a select dropdown for changing them according to the options value.
A new value wasn't assigned but existing value was mutated.
Getter-only computed ref has read-only value property that contains unmodified value.
If the intention is to make ref value deeply read-only, this needs to be explicitly done:
const selectDisplayValues = readonly(toRef(store.getters, 'expense/getSelectDisplayValues'))
I would personaly recommand using mapGetters from vuex : the mapgetters helper
I works like this :
You decalre a getter un your vuex store :
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
}
}
})
And in your vue component :
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters({
doneCount: 'doneTodosCount'
})
}
}
You can then access value from your getter with this.doneCount
If the store change the computed value wil automatically change

How to compute a property based on an object with fallback

I have a component that receives an object as prop, like this:
props: ['propObject']
Then, there's a default object defined (I use VueX, so it's actually defined as a $store getter, but to make it simpler, let's say it's defined in the data method) in the data:
data() {
return {
dataObject: {defaultValueA: 1, defaultValueB: 2}
}
}
And I'd like to have a computed property that would behavior like this:
computed: {
computedObject() {
return Object.values(this.propObject).length > 0 ? this.propObject : this.dataObject;
}
}
However, I know this is not possible because Vue watchers don't watch for changes in the key/value pairs of an object.
I have tried to go with a watched property, like this:
props: ['propObject'],
data() {
return {
object: {},
defaultObject: {}
}
},
watch: {
propObject: {
handler: function() {
this.setComputedObject();
},
deep: true
}
},
methods: {
setComputedObject() {
this.object = Object.values(this.propObject).length > 0 ? this.propObject : this.defaultObject;
}
},
mounted() {
this.setComputedObject();
}
However, the watcher handler is not being called at all when the propObject changes, but if I call it directly via console, it works. Is there any way that I can make the computedObject become reactive?
you need to use Vue.set/vm.$set where you change the props (in source component)
for example
changeProp(){
this.$set(propObject,'newprop','newval');
}
and then just you regualr compouted in the target component (the component which receive the prop)
source : https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats

Computed property was assigned to but it has no setter

What is the correct syntax/hooks to make this work for myVal?
My code looks like this:
<v-item-group v-model="myVal" ...
import { mapActions, mapGetters } from 'vuex';
export default {
computed : {
...mapActions({
myVal: 'myModulePath/setMyVal'
}),
...mapGetters({
myVal: 'myModulePath/getMyVal'
}),
},
}
The store looks like:
actions: {
setMyVal({commit}, value){commit('someMutation',value);}
getters: {
getMyVal: state => { return state.myVal;}
I'm not sure how to wire it so the 'setter' works and the error message goes away.
I've also tried this to no avail:
...mapState('myModulePath', ['myVal'])
You need to define a single computed with a get and a set function. Maybe:
export default {
computed : {
myVal: {
get() { return this.$store.getters.getMyVal; },
set(newValue) { this.$store.dispatch('setMyVal', newValue); }
}
},
}
You need to tell the vue component what to do when the computed property is assigned a new value
computed: {
myVal: {
get: () => this.$state.store.getters.myModulePath.getMyVal,
set: (value) => this.$state.commit('someMutation', value )
}
}
Note that I use the setter instead of the action. Using an action in the computed property setter is a bad idea because actions are usually asynchronous and can cause headaches trying to debug the computed property later.

How to set computed property a value if it's not bound to component's data

I have a component that has a computed property which gets its value from the Vuex store, like following:
computed: {
details () {
return this.$store.getters.getDetails
}
}
getDetails getter returns an object with several properties.
Now the problem is, how to update properties of 'details' object in the component where it is defined?
If it was through the UI, then it could be done via v-model. But it's needs to be done via component's methods. Like the following:
methods: {
someMethod () {
// here I need to update props of 'details' object, but how?
}
}
Since you're using Vuex, do it in the store.
Let's say your details object is like this:
details: { foo: 1, bar: 2 }
Then add a mutation for modifying the details object in the state (I used and because I don't know whether you only want to modify a property of the details or actually want to assign a new object to it):
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
details: { foo: 1, bar: 2 }
},
getters: {
getDetails: (state, getters) => {
return state.details;
}
},
mutations: {
UPDATE_DETAILS (state, payload) {
this.$set(state.details, payload.key, payload.val)
},
REPLACE_DETAILS (state, payload) {
state.details = payload
}
}
});
Then in your component:
// ...
methods: {
// ...
updateDetails(key, val) {
this.$store.commit('UPDATE_DETAILS', { key, val });
},
replaceDetails(obj) {
this.$store.commit('UPDATE_DETAILS', obj);
}
}
Update: What I said is basically a longer explanation of what #Bert was trying to say in his comment.

mapState with setter

I would like to assign setter methods via mapState. I currently use a workaround where I name the variable that I am interested in (todo) as a temporary name (storetodo) and then refer to it in another computed variable todo.
methods: {
...mapMutations([
'clearTodo',
'updateTodo'
])
},
computed: {
...mapState({
storetodo: state => state.todos.todo
}),
todo: {
get () { return this.storetodo},
set (value) { this.updateTodo(value) }
}
}
I would like to skip the extra step and define the getter, setter directly within mapState.
Why would I want to do this?
The normal approach would be use mapMutations/mapActions & mapState/mapGetters
without the computed get/set combination that I have illustrated above and to reference the mutation directly in the HTML:
<input v-model='todo' v-on:keyup.stop='updateTodo($event.target.value)' />
The getter/setter version allows me to simply write:
<input v-model='todo' />
You can't use a getter/setter format in the mapState
what you can try is directly return the state in your get() and remove mapState from the computed property
computed: {
todo: {
get () { return this.$store.state.todos.todo},
set (value) { this.updateTodo(value) }
}
}
Here is a related but not same JsFiddle example
This is my current workaround. Copied from my personal working project
// in some utils/vuex.js file
export const mapSetter = (state, setters = {}) => (
Object.keys(state).reduce((acc, stateName) => {
acc[stateName] = {
get: state[stateName],
};
// check if setter exists
if (setters[stateName]) {
acc[stateName].set = setters[stateName];
}
return acc;
}, {})
);
In your component.vue file
import { mapSetter } from 'path/to/utils/vuex.js';
export default {
name: 'ComponentName',
computed: {
...mapSetter(
mapState({
result: ({ ITEMS }) => ITEMS.result,
total: ({ ITEMS }) => ITEMS.total,
current: ({ ITEMS }) => ITEMS.page,
limit: ({ ITEMS }) => ITEMS.limit,
}),
{
limit(payload) {
this.$store.dispatch({ type: TYPES.SET_LIMIT, payload });
},
},
)
},
}
now you can use the v-model bindings. l
Another way of approaching that is using store mutations like below:
//in your component js file:
this.$store.commit('setStoretodo', storetodo)
Assuming you define setStoretodo in mutations of your vuex store instance (which is something recommended to have anyways):
//in your vuex store js file:
state:{...},
actions: {...}
...
mutations: {
setStoretodo(state, val){
state.storetodo = val
},
...
}
...
That keeps the property reactive as mapState will grab the updated value and it will be rendered automatically.
Surely, that's not as cool as just writing this.storetodo = newValue, but maybe someone will find that helpful as well.