Vuex mutations without referencing state directly? - vue.js

I am learning Vuex and my understanding so far has been that mutations should be simple functions that update the state directly using something like state.property = value or state.object = {payload}, e.g.:
SET_USER_DATA (state, userData) {
state.user = userData
}
I am working through a course from Vue Mastery that contains mutations that look like the code below which does not reference the state object within the body of the mutation at all:
CREATE_TASK(state, { tasks, name }) {
tasks.push({ name, id: generateId(), description: "" });
},
UPDATE_TASK(state, { task, field, value }) {
Vue.set(task, field, value);
},
CREATE_TASK creates a new empty task and adds it to the tasks array using tasks.push(), but shouldn't a reference to the state object be required to update the state? E.g. state.tasks.push()? How does simply pushing an item onto a bare array commit the change to the state?
In the second example, they use Vue.set() to update the value of a specific field within a task (e.g. name, description), but again, there is no reference to the state object here.
The best I can figure is that they relying on Vue's native reactivity to automatically update the state when calling Vue.set() or Array.push(). But if that is the case, wouldn't any usage of Vue.set() or Array.push() inside of a component also immediately update the state (violating the rule that state changes should only be handled within a mutation?

Related

How can/Should I prevent actions from mutating the state in vuex?

We should update the state in mutations and these mutations can be called inside actions. That's OK. But, look at the example below:
actions: {
markMessageAsRead({ state, commit }, payload) {
const messageId = payload.messageId
const messages = state.messages[payload.chatId]
const message = messages[messageId]
message.status = 'read' // <---- THIS ALREADY MUTATES THE STATE!
commit('UPDATE_MESSAGE', { messageId, message }) // <---- BEFORE THIS
},
}
In the above example, before the commit(...) line, the message is getting updated already, because of the message.status = 'read' line.
So, to prevent mutating state in actions, should I always copy an object from the state before changing its props? Is there anything I'm wrong? What's the correct way to get something from state and update it? Hope I explained it well.
If you are updating the payload in action to make your mutations more reusable, then the reusability of a single mutation instead of a more targeted one (SET_STATUS for ex.) does not justify the complexity added by cloning the state before mutating it (look at the code below, it is more clean and simple).
For me, it is a more maintainable and scalable approach to have more targeted mutations, updating only a single prop (when the only purpose is to update a single prop), because this avoid the unnecessary need to always account for every change made to store state as long as the targeted property exists on object.
Thus, in your situation, since your action is called with the only purpose to mark the message as read, I would make a SET_STATUS mutation, and avoid any other manipulations of the state outside mutation.
mutations: {
SET_STATUS(state, {messageId, chatId, status){
state.messages[chatId][messageId].status = status
}
}
actions: {
markMessageAsRead({ state, commit }, payload) {
commit('SET_STATUS', { messageId: payload.messageId, chatId: payload.chatId, status: 'read' })
},
}

mapstate to dynamic state objects of vuex store from the component

I am trying to build a watchlist (data streaming program) by vue3 with vuex. When a watchlist component subscribes for a symbol it should receive updates for that symbol from the store. When removing the subscription from the component that particular component should not receive that state change after that. We cannot hardcode symbol names in store to mapstate from component for each individually since there can be hundreds. if we take all the symbols as an attribute of a single object and map the state to it it will be a performance overhead since not all watchLists are referring to all the symbols.
So my question is there any way to inject a dynamically changing array to mapstate?
In component->
computed: {
...mapState([this should change dynamically]),
},
in Store->
state : {
these states also should be dynamic
},
or is there any workaround in vue to achive this?
I have found an alternative way by using getters. When we are using that we don't have to map state at all.
I am returning a function from getters with an argument since Vuex getters don't accept arguments.
symbol: (state) => (symbol) => {
return state.payload[symbol]
}
and from the component, I am watching for that getter.
this.$store.watch(
(state, getters) => getters.allSymbols(currency),
(newValue, oldValue) => {
this.symbolObjects[symbol] = newValue;
// do something
},
);
the watch is triggering every time the currency update. Also using the Vuex watcher gives the benefit of accessing both old and new values.

How do I make a state getter reactive when a dispatched action sets a state object with Vue.set?

I have a button that’s set to update the a store object using Vue.set but the getter for that same piece of data in a different component isn’t reactive until I change the state using a different component method.
The state object in question is set up as a hash that's keyed by UUID's. The object is generated and then added to the state object with Vue.set
The button is set to dispatch an action, which I see it going through immedietely in the devtool, that does this:
mutations: {
COMPLETE_STEP(state, uuid) {
let chat = state.chatStates[uuid];
let step = chat.currentStep;
Vue.set(chat.data[step], "complete", true);
}
},
actions: {
completeStep({ commit }, uuid) {
commit("COMPLETE_STEP", uuid);
}
},
Now, when I want to grab that data, I have a getter that grabs that data. This doesn't run until I do something else that causes a re-render:
getters: {
getChatStepComplete: state => (uuid, step) => {
let chatState = state.chatStates[uuid];
return chatState.data[step].complete;
},
}
I want the getter to show the updated change right away instead of waiting to update on a different re-render. How do I make that happen?
Figured out my issue: I wasn’t creating the data array when I first create and add chat to the state. Once I started initializing it to an empty array, it’s started being reactive.

Vuex: Weird question on weird behavior of vuex. I need at least one mutation and a commit to update or assign to my store objects

I'm trying to set token to my store.token I know this is not a best option without using mutation but I'm doing something like this:
methods : {
molestor(){
const self = this;
this.$store.state.token = "new token";
this.$store.state.cleavage= "yes";
this.$store.commit('settoken', "somethingrandom");
},
}
Then on my store.js:
export const store = new Vuex.Store({
state : {
token : '',
},
mutations : {
settoken(state,token){
console.log(token);
}
}
});
Right now it works fine... it sets up. But when I remove the mutation from store.js or remove the commit on my molester() it wont assign the value to token. Why is this happening?
To set the value of state in store, we have to interact with Vuex api via mutations/commits.
By trying to set the state without a mutation, this goes against the design of Vuex (having a manageable store/state)
Typically trying to set state without mutations (say within an action) will throw an error, but I also believe that by getting the state via ‘$store.state’ will only return the state (and not return the instance of state)
This is done to maintain immutability throughout your application state
If you're expecting to see the change appear in the Vue dev tools you won't see any changes to state unless they occur through a mutation or until another mutation is called.

Vuex Mutation target passed to payload instead of looking for it in the state

Let's say the vuex state has a structure like this:
state: {
houses: [{
pk: 1,
historicalName: 'Poo'
}],
},
And house instances from the state are used by passing it as props to components (houses.vue template):
<div><house v-for="hs in housesState" :house="hs"></house></div>
Then when I need to mutate fields of house, do I write mutation like this:
[Types.mutations.SET_HISTORICAL_NAME]: (state, payload) => {
state.houses.find(x => x.pk === payload.house.pk).historicalName = payload.historicalName
}
or do I pass the instance of the house from component
this.$store.commit(Types.mutations.SET_HISTORICAL_NAME, {house: this.house, historicalName: 'Gold'})
and update it without searching the array?
[Types.mutations.SET_HISTORICAL_NAME]: (state, payload) => {
payload.house.historicalName = payload.historicalName
}
Will the second option cause any locks in the state? Mutation could be called simultaneously for different houses. Will it be faster? Not searching the array every time should help but I'm not sure it's that straightforward here.
House data is also used by another components (I've simplified data structure in the question) so I don't think I can only store house on local component level.