Computed Property does not get updated when state changes - vuejs2

We are trying to detect whether a person is logged in or not using the vuex store state: loggedIn. When I call the API service from the action it calls the mutation after successful login and changes the data in the state:
loginSuccess(state, accessToken) {
state.accessToken = accessToken;
state.authenticating = false;
state.loggedIn = true;
console.log(state.loggedIn);
}
The console.log() shows the value, so the mutation is working.
In my other component, I use a computed property to watch for changes in the store using ...mapState() and bound the property in the template view:
computed: {
...mapState('authStore',['loggedIn' ]);
}
But the view never gets updated based on the computed property. I checked using the Vue dev tools in the console. It shows the state changes.
I have initialized the state.
export const states = {
loggedIn: false
};
I have tried to call the state directly.
this.$store.state.authStore.loggedIn;
I have tried different approaches.
...mapState('authStore', { logging:'loggedIn' });
//or
...mapState('authStore',['loggedIn' ]);
also, tried watch: {} hook but not working.
Interestingly though, the state's getter always shows undefined, but the state property changes in the dev tools.
Cannot figure out what is wrong or how to move further.
here is the screenshot of devtools state after successful login:

This catches my eye:
export const states = {
loggedIn: false
};
My suspicion is that you're then trying to use it something like this:
const store = {
states,
mutations,
actions,
getters
}
This won't work because it needs to be called state and not states. The result will be that loggedIn is unreactive and has an initial value of undefined. Any computed properties, including the store's getter, will not be refreshed when the value changes.
Whether my theory is right or not, I suggest adding console.log(state.loggedIn); to the beginning of loginSucess to confirm the state prior to the mutation.

Related

How can I keep the state saved when I coming to same route component from another in vuejs?

I have a claims dropdown and function called on change of dropdown value is like this:
changeDdVal(v) {
this.$store.dispatch('getUserRoles').then(userRoles => {
if (userRoles.admin) {
this.selectedYear = new Date().getFullYear();
switch (v) {
case 1:
this.dropValue = "salesClaimCount";
this.headerDropdownClaim = "sales";
this.tableHeadData = this.salesTableHeadData;
this.api.filterData = "getSalesClaimFilterDataForApprover";
this.$router.push({name: this.routeName, params: {approverClaimType: this.claimTableData.claimStatus, dropType: this.headerDropdownClaim, userRole: this.role}})
.catch(() => {});
this.yearChange(this.selectedYear);
break;
case 2:
this.dropValue = "staffingClaimCount";
this.headerDropdownClaim = "staffing";
this.tableHeadData = this.placementTableHeadData;
this.api.filterData = "getStaffingClaimFilterDataForApprover";
this.$router.push({name: this.routeName, params: {approverClaimType: this.claimTableData.claimStatus, dropType: this.headerDropdownClaim, userRole: this.role}})
.catch(() => {});
this.yearChange(this.selectedYear);
break;
}
}
})
}
And this yearChange function is taking care of calling api with the selected year and display data accordingly. I am calling this changeDdVal function on created state. When the data is displayed and if I click on any data, I am routing to detail view of that particular record.
Now the problem is when I change the year to any other from current year and click on that particular year data and come back to dashboard using "$router.go(-1)", my states resets i.e the year changes to current again but I want to remain in the selected year.
I know this is happening because of the "this.selectedYear = new Date().getFullYear();" and I can use vuex to store the state instead of this.
But in my case I want to reset the state on drop change and save the state if I come from the detail view of that record of selected year.
For example, by default the year is 2022 and I change it to 2021 the data is shown of 2021 and I click on any record of 2021 and use $router.go(-1) on that I should be in 2021. And if I have selected 2021 year and change the drop from sales to placement the year should reset to 2022.
this is my year change function
yearChange() {
this.axiosCancelToken.cancel("cancel requested")
this.changeYear = false;
this.firstLoad = true;
this.sortBy = "claimId";
this.sortType = "descending";
this.handlePagination(this.paginationOptions);
this.mavePaginationList(false);
this.fetchData();
var dataApi = new FormData();
dataApi.append("year", this.selectedYear);
api
.post(this.api.filterData, {data: dataApi})
.then((res) => {
//resposne
})
},
According to your question-
You need to keep some values saved when changing the route and some values to refresh as per API requests. (keep-alive won't do much in that use case.)
the values which need to persist are either a form variable (select dropdown) or some manual variable (pagination number).
As you said you can't use Vuex because of some problems, My guess, is that Vuex and vuex-map-fields together should ease the process of your problem.
vuex-map-fields is a good way to enable two-way data binding for fields saved in a Vuex store.
If I take your question as an example-
Suppose at your Home.vue you change the dropdown value, and on its changes, an API request gets fired and brings some data which you display on your component, and you can select any data which will lead to a new route, let's say About.vue and when you come back to Home.vue the year you selected should persist.
So, you can do this by like this-
Install the vuex-map-fields and configure it properly.
Create a property in your Vuex state of name, "selectedYear", like this-
import Vuex from "vuex";
// Import the `getField` getter and the `updateField`
// mutation function from the `vuex-map-fields` module.
import { getField, updateField } from "vuex-map-fields";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
selectedYear: null
},
getters: {
// Add the `getField` getter to the
// `getters` of your Vuex store instance.
getField
},
mutations: {
// Add the `updateField` mutation to the
// `mutations` of your Vuex store instance.
updateField
}
});
In your Vue template, use this Vuex state property to get and mutate the selectedYear.
<select v-model="selectedYear" #change="callAPI()">
<option>2020</option>
<option>2021</option>
<option>2022</option>
</select>
import { mapFields } from "vuex-map-fields";
export default {
name: "Home",
computed: {
// The `mapFields` function takes an array of
// field names and generates corresponding
// computed properties with getter and setter
// functions for accessing the Vuex store.
...mapFields(["selectedYear"]),
},
}
And when you want to reset the dropdown state, simply do this-
this.selectedYear = null;
It will update in the Vuex state as well.
In that way, you should notice that when you switch the route, the selected box state will persist because this is bonded with the store.
In the same fashion, you can keep the state of pagination, and another variable you want.
The benefit of using vuex-map-field is that you don't need to write getter and setter functions manually to contact the state.
Note- If you want to persist your Vue state between page reloads also, then use vuex-persistedstate with Vuex.
As your code depends on multiple concepts, I cannot provide the snippet here, but I created a fiddle and try to reproduce the problem, you can look at it, and modify it according to your case and make sure things work.
In the fiddle, I used the same problem mentioned in your question (but the data and API request is dummy), and you should see when you change the routes (from Home.vue to About.vue), the year would persist but when reload the page it won't because I didn't use vuex-persistedstate.
Here it is- https://codesandbox.io/s/vue-router-forked-mdvdhd?file=/src/views/Home.vue:935-1181

vuex: do not mutate vuex store state outside mutation

I have the following scenario:
Component Textfield:
<v-text-field
v-model="form.profile.mobile_business"
label="Mobile"
prepend-inner-icon="mdi-cellphone"
></v-text-field>
I get the current value via:
data() {
return {
form: {
profile: JSON.parse(JSON.stringify(this.$store.getters["user/Profile"])),
},
};
},
I have a submit button that calls this method:
updateUserProfile() {
this.$store.dispatch("user/updateProfile", this.form.profile);
}
Everything works perfect. On my store dispatch I make the API call and update the store via my mutation:
context.commit('UPDATE_PROFILE', profile);
No errors until this step.
But if I change the form input again - after I pressed the submit button, I get:
vuex: do not mutate vuex store state outside mutation
But I don't want to change the vuex store just when I change the value on my form input.
It should only be updated if someone hits the submit button.
v-model provides 2-way data binding. Changing anything in the view will automatically attempt to update the model directly, rather than through a mutation. Thankfully, Vue allows get and set on computed properties to help us past that.
What you should do on your textfield component is add a computed property with get and set methods. It will look something like this:
computed: {
userProfile: {
get() {
JSON.parse(JSON.stringify(this.$store.getters["user/Profile"]));
},
set() {
// only commit the changes to the form, do not submit the action that calls the API here.
this.$store.commit("user/updateProfile", this.form.profile)
}
}
Your v-model attribute should then be set to this newly created property, and any 'set' operations (read: a user changing the input value) will call the action as opposed to attempting to set the value in the Store directly.
Here is a live example: CodePen
I solved it this way:
form: {
profile: _.cloneDeep(this.$store.getters['user/Profile'])
},
and added a watch handler:
form: {
handler: _.debounce(function (form) {
console.log("watch fired");
}, 500), deep: true
}
so if the user changes the value, nothing happens (except my console.log action).
if he presses the submit button, the store dispatch action will be fired.

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.

How can I directly set value in state of VueX

I just want to change data in state of VueX without pass value through following step Action > Mutation > State then getData from state of VueX in other component, Is it possible to do or anyone has another best way to do send value with array to ...mapAction please explain me,
Actually, I just want to send data with array to other component which the data will be change every time when user selected checkbox on Treevue component that I used it.
Thank a lot.
## FilterList.vue ##
export default {
data() {
return {
listSelected: ['aa','bb','cc','...'], // this value will mutate when user has selected checkbox
}
}
}
=================================================================
## store.js ##
export default new Vuex.Store({
state = {
dataSelected: [ ]
},
mutation = {
FILTERSELECTED(state, payload) {
state.selected = payload
}
},
action = {
hasSelected(context,param) {
context.commit('FILTERSELECTED',param)
}
},
getters = {
getSelected: state => state.dataSelected,
}
strict: true
})
You can set strict: false and change data directly, but I wouldn't recommend it.
You'll lose the benefit Vuex provides, i'd rather share that object outside vuex.
Not every change needs to be synced with the store, it depends on the scenario.
For a EditUser component as example, I'll start with a deep copy of the user object from the store:
this.tmpUser = JSON.parse(JSON.stringify(this.$store.state.user))
This tmpUser is disconnected from the store and won't generate warnings (or updates) when you change its properties.
When the user presses the "save" button, i'll send the changed object back to the store:
this.$store.dispatch("user/save", this.tmpUser)
Which updated the instance in the store and allows the other parts of the application to see the changes.
I also only write actions when async (fetching/saving data) is needed.
For the sync operations I only write the mutations and the use the mapMutations helper or call $store.commit("mutation") directly.

Emit an event when a specific piece of state changes in vuex store

I have a Vuex store with the following state:
state: {
authed: false,
id: false
}
Inside a component I want to watch for changes to the authed state and send an AJAX call to the server. It needs to be done in various components.
I tried using store.watch(), but that fires when either id or authed changes. I also noticed, it's different from vm.$watch in that you can't specify a property. When i tried to do this:
store.watch('authed', function(newValue, oldValue){
//some code
});
I got this error:
[vuex] store.watch only accepts a function.
Any help is appreciated!
Just set a getter for the authed state in your component and watch that local getter:
watch: {
'authed': function () {
...
}
}
Or you can use ...
let suscribe = store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
// call suscribe() for unsuscribe
https://vuex.vuejs.org/api/#subscribe