I am trying to update the store state using mutation. But the problem is that the state remains same after mutation and is updated only in scope of that mutation. It acting more of like passing arguments to a function.
The expectations are the state in the store should be updated but it's not happening. The store state is not mutated at all but the argument state is being mutated.
Here is the sample code of my implementation. I am using modules for Vuex Store and this is from
user.store.js
export const user = {
state: {
user: {},
isAuthenticated: false
},
mutations: {
updateUser(state, payload) {
Vue.set(state.user, payload.key, payload.value);
}
}
}
Edit 2:
I got to the fix of the issue and it is due to similar naming for the user store module and the state.user object.
After i changed the name of the store module, the issue was resolved.
Thank You for you contributions.
Try to use Vue.set in your mutation.
For example:
state: {
isInteractionEnabled: false
},
mutations: {
SWITCH_INTERACTION: (state, {status}) => Vue.set(state, 'isInteractionEnabled', status)
},
Related
I am using Vuex for state right now and taking advantage of getters to acquire state so that I don't have to use props. However, I am curious if I can use Vuex to replace this type of function emitting. How would that be done with Vuex, if it's even possible.
Parent
<child-component #handleselectproduct="selectProduct"></child-component>
selectProduct: function() {
axios.get()
}
Child
<button #click="selectProduct></button>
selectProduct: function() {
this.$emit('handleselectproductselection');
}
You could use vuex actions and mutations. Mutations are used for synchronous and actions for asynchronous calls. You could imagine them as setters as opposed to getters that you already use. So in your current example, you would call an action which may or may not set a state property through a mutation. you would define this action with:
const store = new Vuex.Store({
state: {
selectedProduct: {}
},
getters: {
getSelectedProduct: state => {
return state.selectedProduct
}
},
mutations: {
selectProduct(state, payload) {
state.selectedProduct = payload
}
},
actions: {
async selectProduct(context, axios) {
const { commit } = context
const product = await axios.get(...) // some call
commit('selectProduct', product)
}
}
})
After defining these, you can call the action through the this.$store.dispatch('selectProduct', axios) method in the child component and have your result available in the parent component or wherever else you may need it. And if you need some sort of notification that the property has changed (you need to do some change to the selectedProduct data and then show it), you can set a watcher function on the respective getter or just use computed properties that use the getter and they will pick up the change.
You can find out more about actions at https://vuex.vuejs.org/guide/actions.html
I am trying to logout and purge the store at the same time, so on click I dispatch this:
dispatch({type: PURGE, key: 'root', result: () => { } });
Redux persist catches it, and reports purging the store. Great.
In another reducer I catch that dispatch, and remove my access token like this:
import { PURGE } from 'redux-persist/es/constants';
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAccessToken(state: AuthState, action: PayloadAction<Auth>): void {
state.accessToken = action.payload.accessToken;
state.expiresIn = action.payload.expiresIn;
},
},
extraReducers: {
[PURGE]: (state: AuthState, action: string): void => {
state.accessToken = initialState.accessToken;
state.expiresIn = initialState.expiresIn;
},
},
});
The PURGE reducer actually is called, and modifies the state, but still no re-rendering happens. so redux must not pick that up. But according to the docs the Redux toolkit uses a Proxy object for the state and does a comparison to see if it's modified.
Things I tried:
state = initialState;
and
state = { ...initialState };
Didn't work. The store works, and holds data, other actions work. How do I proceed?
EDIT: Further debugging revealed that my own reducer was called BEFORE the redux-persist reducer, and redux-logger reported that my reducer did not change the state at all.
I'm facing a similar issue (not re-rendering) and came by this thread today:
Seems like you can't replace state objects entirely.
From: https://redux-toolkit.js.org/usage/immer-reducers
Sometimes you may want to replace the
entire existing state, either because you've loaded some new data, or
you want to reset the state back to its initial value.
WARNING A common mistake is to try assigning state = someValue
directly. This will not work! This only points the local state
variable to a different reference. That is neither mutating the
existing state object/array in memory, nor returning an entirely new
value, so Immer does not make any actual changes.
const initialState = []
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
brokenTodosLoadedReducer(state, action) {
// ❌ ERROR: does not actually mutate or return anything new!
state = action.payload
},
fixedTodosLoadedReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return action.payload
},
correctResetTodosReducer(state, action) {
// ✅ CORRECT: returns a new value to replace the old one
return initialState
},
},
})
So
state = initialState;
would be
return initialState;
This turned out to be the solution:
extraReducers: {
[PURGE]: (state: UserState, action: string): UserState => ({
...state,
...initialState,
}),
},
I don't understand why, as modifying the state object should work too, according to the documentation:
To make things easier, createReducer uses immer to let you write
reducers as if they were mutating the state directly. In reality, the
reducer receives a proxy state that translates all mutations into
equivalent copy operations.
I'm trying to figure out how to properly update a getter value when some other variable from VueX changes/updates.
Currently I'm using this way in a component to update:
watch: {
dates () {
this.$set(this.linedata[0].chartOptions.xAxis,"categories",this.dates)
}
}
So my getter linedata should be updated with dates value whenever dates changes. dates is state variable from VueX store.
The thing is with this method the value won't be properly updated when I changed route/go to different components. So I think it's better to do this kind of thing using the VueX store.
dates is updated with an API call, so I use an action to update it.
So the question is how can I do such an update from the VueX store?
EDIT:
I tried moving this to VueX:
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit("SET_DATA", {
this.linedata[0].chartOptions.xAxis,"categories": response.data.dates1,
this.linedata[1].chartOptions.xAxis,"categories": response.data.dates2
});
}
SET_DATA(state, payload) {
state = Object.assign(state, payload);
}
But the above does not work, as I cannot set nested object in action this way...
Getters are generally for getting, not setting. They are like computed for Vuex, which return calculated data. They update automatically when reactive contents change. So it's probably best to rethink the design so that only state needs to be updated. Either way, Vuex should be updated only with actions/mutations
Given your example and the info from all your comments, using linedata as state, your action and mutation would look something like this:
actions: {
async loadData({ commit }) {
let response = await Api().get("/cpu");
commit('SET_DATA', response.data.dates);
}
}
mutations: {
SET_DATA(state, dates) {
Vue.set(state.linedata[0].chartOptions.xAxis, 'categories', dates[0]);
Vue.set(state.linedata[1].chartOptions.xAxis, 'categories', dates[1]);
}
}
Which you could call, in the component for example, like:
this.$store.dispatch('loadData');
Using Vue.set is necessary for change detection in this case and requires the following import:
import Vue from 'vue';
Theoretically, there should be a better way to design your backend API so that you can just set state.linedata = payload in the mutation, but this will work with what you have.
Here is a simple example of a Vuex store for an user.
export const state = () => ({
user: {}
})
export const mutations = {
set(state, user) {
state.user = user
},
unset(state) {
state.user = {}
},
patch(state, user) {
state.user = Object.assign({}, state.user, user)
}
}
export const actions = {
async set({ commit }) {
// TODO: Get user...
commit('set', user)
},
unset({ commit }) {
commit('unset')
},
patch({ commit }, user) {
commit('patch', user)
}
}
export const getters = {
get(state) {
return state.user
}
}
If you want to set the user data, you can call await this.$store.dispatch('user/set') in any Vue instance. For patching the data you could call this.$store.dispatch('user/patch', newUserData).
The getter is then reactively updated in any Vue instance where it is mapped. You should use the function mapGetters from Vuex in the computed properties. Here is an example.
...
computed: {
...mapGetters({
user: 'user/get'
})
}
...
The three dots ... before the function call is destructuring assignment, which will map all the properties that will the function return in an object to computed properties. Those will then be reactively updated whenever you call dispatch on the user store.
Take a look at Vuex documentation for a more in depth explanation.
I have empty state in the beginning like so
new Vuex.Store({
state: {
comboBoxNewValues: {}
},
Over time, there are mutations which changes the state like so
this.$store.commit('addComboBoxValues', { input: 'foo', value: ['value': 1, 'name': 'bar']});
Using the following mutation code
mutations: {
addComboBoxValues(state, _value) {
state.comboBoxNewValues[_value.input] = _value['value'];
},
}
It works perfectly and mutations also changes the state, Now I want to perform some action on change of the state so in my component I added a computed property like so
computed: {
getComboBoxNewValues() {
return this.$store.state.comboBoxNewValues;
}
}
When I debug using vue-dev-tools, the components computed property has data. Whenever the data changes using mutation in the vuex, that data is reflected in the computed property as well.
Now I add a watcher to it like so
watch: {
getComboBoxNewValues:{
handler: function(to, from) {
console.log("reload");
},
deep: true
}
}
The problem is that the never never gets called, no matter how many times the data has changed in the computed property. Please advice on what I am missing.
I am modifying hacker-news example and I added notifications component (If fetching external data goes wrong: save notification with error in vuex, after user reads it and clicks X remove it from vuex state). Looks like this:
[NOTIFY] (state, message) {
state.data.push(message)
},
[READ_NOTIFICATION] (state, index) {
state.data.splice(index, 1)
}
Problem: When data is being fetched and state is being set on server, it keeps error there in global state forever, which means that if I open new browser I will get the same old error from previous session.
Shouldn't server-side vuex state reset on every request? What am I missing?
I'm confused as to what you mean by "server-side state". Vuex is a client side store. What and why are you saving state to the server?
Furthermore, I cannot tell what you are doing with the code? Is that in a store.js file? In the mutations object?
You would need to wrap that in a vue method like so:
In the Vue file:
methods: {
clicksButton (idx) {
this.$store.commit('read_notification', idx)
}
}
In the store.js file:
const store = new Vuex.Store({
state: {
data: [],
error: null
},
mutations: {
read_notification (state, idx) {
state.data.splice(idx, 1)
}
}
}