Watching and tracking state changes with Vuex inside child components - vue.js

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).

Related

Accessing computed array from one component in another using VueX

In one component I have this function that is being updated within that component:
computed: {
//filter based on dates
mapfiltered: function() {
return this.mapped.filter(i => this.y.includes(i.date));
},
}
Now in another component I want to access this mapfiltered computed property.
<single-news-template
v-for="m in mapfiltered"
v-bind:key="m.id"
v-bind:title="m.title"
></single-news-template>
I think its possible with VueX but im not sure how can I modify it, so that it could be accessed

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.

Reseting a config option on vue-flatpickr programatically

I have a vue date component that is composed of a vue-flatpickr-component. When I pass config options in as props, of course, they work as expected, however, if want to change one of the config options which should be possible, it won't propagate down. I'm not a Vue guru, any advice would be helpful.
I'm using a page component in a Laravel app, it shouldn't be relevant, however, just in case someone answers with vuex or vue-router, those won't work here.
Here are the form elements in play from page.vue:
<material-select
name="specialist"
label="Specialist"
default-text="CHOOSE HOMEVISIT SPECIALIST"
:options="staffMembers"
v-model="form.specialist"
:validation-error="form.errors.first('specialist')"
class="mb-4"
></material-select>
<div class="w-1/2">
<material-date
label="Appointment date"
name="appointment_date"
v-model="form.appointment_date"
:validation-error="form.errors.first('appointment_date')"
class="mb-4"
:external-options="{
enable: this.appointmentDates,
}"
></material-date>
<pre>{{ this.appointmentDates }}</pre>
</div>
Here is the computed property driving the config change:
computed: {
appointmentDates(){
if(this.form.specialist !== null){
return this.availableDates[this.form.specialist - 1]
}
return []
},
When a different home visit specialist is chosen, it will update with Vue's reactivity.
I have a computed property changing the config options. Here are the props data and the relevant computed property from the MaterialDate.vue file:
import flatPickr from 'vue-flatpickr-component';
import 'flatpickr/dist/flatpickr.css';
export default {
components: {
flatPickr
},
props: {
value: String,
label: String,
validationError: String,
name: {required:true},
optional: {
default: false
},
externalOptions: {}
},
data() {
return {
defaults: {disableMobile: true,},
options: this.externalOptions
}
},
computed: {
config(){
return Object.assign({}, this.defaults, this.options)
},
This will of course never update the enabled dates option because the prop is immutable, I need to get access to the set(option, value) section of the wrapped by vue-flatpickr-component. However, my Vue kungfu is not really strong enough to source dive it to see how I might access it and programatically call set('enabled', [new dates]).
Sometimes, you shouldn't code when you are tired :) But Hopefully this will help someone at some point. I was over thinking this. Data is passed down through props, and if controlling data changes it has to be reflected in the propagated data. Much like v-model with it's value prop.
So instead of binding the config object on this.options which doesn't stay hooked to it's prop value that it was initialized from, the computed function should be calculated from the prop which will change based on the new passed in options prop.
so simply change the computed function to:
computed: {
config(){
return Object.assign({}, this.defaults, this. externalOptions)
},
and remove the data element.
... Elementary
Sorry for the cheese it's late and I feel relieved.

Tracking a child state change in Vue.js

I have a component whose purpose is to display a list of items and let the user select one or more of the items.
This component is populated from a backend API and fed by a parent component with props.
However, since the data passed from the prop doesn't have the format I want, I need to transform it and provide a viewmodel with a computed property.
I'm able to render the list and handle selections by using v-on:click, but when I set selected=true the list is not updated to reflect the change in state of the child.
I assume this is because children property changes are not tracked by Vue.js and I probably need to use a watcher or something, but this doesn't seem right. It seems too cumbersome for a trivial operation so I must assume I'm missing something.
Here's the full repro: https://codesandbox.io/s/1q17yo446q
By clicking on Plan 1 or Plan 2 you will see it being selected in the console, but it won't reflect in the rendered list.
Any suggestions?
In your example, vm is a computed property.
If you want it to be reactive, you you have to declare it upfront, empty.
Read more here: reactivity in depth.
Here's your example working.
Alternatively, if your member is coming from parent component, through propsData (i.e.: :member="member"), you want to move the mapper from beforeMount in a watch on member. For example:
propsData: {
member: {
type: Object,
default: null
}
},
data: () => ({ vm: {}}),
watch: {
member: {
handler(m) {
if (!m) { this.vm = {}; } else {
this.vm = {
memberName: m.name,
subscriptions: m.subscriptions.map(s => ({ ...s }))
};
}
},
immediate: true
}
}

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.