VueJS - Can't assign value from mapState to data property after reloading the page - vue.js

Can't assign the value from mapState to data property after reloading the page, it works if you go to the child page but not if you are already standing in the child page and reloading the browser.
Computed mapState
computed: {
...mapState({
tsStore: state => state.SchemeStore
})
}
Data Property
data () {
return {
works: '',
offTime: '',
}
}
Mounted
if (this.tsStore.singleView) {
// Set data based on api.
let single = this.tsStore.singleView
this.works = single.works
this.offTime = single.offTime
}
After reloading works and offTime get empty in the data property.

Yes, the problem is the state being updated after the component was mounted;
So, the updated method is called instead of mounted.
It is visible in this fiddle, where the API call is simulated by the setTimeout:
https://jsfiddle.net/eywraw8t/369915/
I think the best way to get the component updated is using computed properties, where Vue implements proxies to watch for changes, like this fiddle:
https://jsfiddle.net/3mn2xgvr/
I moved the changes to a computed properties so when the state in Vuex changes, all data that depends on that changes.

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

My component does not react to change Vue with Vuex

I have a state in the Vuex store that reacts to the change in the button and everything changes its dependent value, the problem is that I am using it in a component and I call it in the data()
data() {
return {
currency: this.$store.state.currency,
}
}
When mounting the component, everything is displayed fine, because it takes the value from storage, but when I update that value, the change is not reflected in the component.
I would like to know how I could make the variable render again with the new value of the storage, every time it changes.
I tried using the currency variable where I save the data, it did not work and I also tried to place the variable directly where it is used, but it did not work either
computed: {
currency() {
return this.$store.state.currency;
},
},
Do it like that.

Which lifecycle hook to use on child component to run after parent in Vuejs?

On the parent I'm running this:
mounted(){
this.$store.dispatch('fetchNotesAction')
},
components: { ChildComponent }
With that, the Vuex store is populated with data.
I'm assuming, therefore, by the time the child component runs, the store is already full.
In the ChildComponent I'm trying this
mounted(){
console.log(this.$store.getters.getNotes)
},
If I log the exact same code inside the parent, after the dispatch it display the data. So the code (the connection) behind those lines work fine, just am I correct in assuming that I don't need to rerun the fetch (from fetchNotesAction) again?
I tried created instead of mounted and the same thing
The best practice is to use a computed property that returns your state getter then use watch property to observe its changes in the child component :
computed:{
notes(){
return this.$store.getters.getNotes;
}
},
watch:{
notes(newval,oldVal)'
console.log(newval)
}
}

Computed value based on component's created method

In my component I have acreated method where I make a request and then I want to use the data I get for a computed property.
computed: {
...mapState(["pages"]),
pageContent() {
let slug = this.$route.params.slug ? this.$route.params.slug : 'home' ;
let page = this.pages.find(page => page.slug == slug);
return page.content;
}
},
methods: {
...mapActions(['setPagesAction'])
},
created() {
this.setPagesAction();
}
The problem is that created is executed after the computed property pageContent is read, so it is undefined.
How can I make pageContent get the data from created ?
Solution
As you know, computed property will update itself when the dependent data changes. ie when this.pages is assigned in vuex. So
You have mainly three options.
Option 1
Set a variable so that till the time page content is being loaded, a loading spinner etc is being shown.
Option 2
If you are using vue-router, then use the beforeRouteEnter guard to load the data.
like here
Option 3
Load data initially (when app starts) and add it to vuex. (you can use vuex modules for seperating data store if needed)

Watching and tracking state changes with Vuex inside child components

This is more a "which method should I use" question, rather than a how to.
I have the following in my Vuex.Store() instance:
store.js:
export default new Vuex.Store({
state: {
acceptedTermsAndConditions: false
},
})
From various components I'm emitting an event which sets this.$store.state.acceptedTermsAndConditions to true or false, dependent on different User inputs.
However, in my component I would set the checked value of a "Accepts T&Cs" checkbox to this value, something like this:
components/Component.Vue:
data () {
return {
form: {
checkboxTermsAndConditions: this.$store.state.acceptedTermsAndConditions
}
}
}
I'm just not sure what method handles this? Does a solution require a getter? If not, what is the best way to watch for state changes and set data values accordingly?
If you want to set the checkbox state based on the stored value, you should use the computed object and the mapGetters helper function in your component:
https://vuex.vuejs.org/guide/getters.html#the-mapgetters-helper
computed: {
...mapGetters(['acceptedTermsAndConditions'])
}
Like this, the value will be accessible in your component. If you want to do the contrary (refresh the store based on the checkbox value), you should create a mutation in your store and you should use this in your component:
methods: {
...mapMutations(['setTACcheckbox'])
}
This way, inside your component you can refresh the store value with this.setTACcheckbox(value).