How to watch for vuex state? - vue.js

I need do fire a function within component when my vuex state data change, but it does not work , is there any wrong usage about watch hook for vuex?
const state = {
currentQueryParameter:[],
};
const mutations = {
currentQueryParameter(state,info){
state.currentQueryParameter[info.index]=info.value
Vue.set(info, info.index, info.value);
}
}
in component
watch: {
'$store.state.currentQueryParameter': function() {
console.log("changed")
this.getData()
}
},

What you are doing is technically correct and will work.
However several thing are still going wrong:
If you want your state to be reactive you need to fill Arrays with native array methods (.push(), .splice() etc). Vue.set() is only used to set Object properties.
You are watching currentQueryParameter, which is an Array. Its value does not change through your mutation - it stays the same Array. If you want to watch nested elements as well, you need to use the deep flag in your watcher, like so:
watch: {
'$store.state.currentQueryParameter': {
deep: true,
handler(newVal) {
console.log('queryParameter changed');
}
}
}
I don't know what you are trying to do with this one in your mutation:
Vue.set(info, info.index, info.value); but you should not mutate the properties you pass to a function.

Related

Vuex: How to replace function emitting between parent and child with Vuex?

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

Changing getter value using state value in Vuex

I have this getter that get the value of one item in the state:
boxdata: state => {
return state.boxchart.data
},
Now I have another item in state that I use to change the value of getter
Currently I do this when component is mounted but it seems that the data sometimes loads but sometimes does not:
computed: {
...mapGetters(["boxdata"]),
...mapState(['reference_fc'])
},
mounted() {
this.boxdata[0].chartOptions.series[0].data[0]=this.reference_fc.NSR.values
}
So I wonder how can I ensure that the boxdata getter is already updated on the first time that the component loads?
Vue cannot detect array element assignments. This is explained in Caveats.
Try this instead:
this.$set(this.boxdata[0].chartOptions.series[0].data, 0, this.reference_fc.NSR.values)
You shouldn't mutate data using getters. You should use mutations.
Getters are only to get derived state based on store state. see here
In your store :
mutations: {
setBoxdata(state, value) {
state.boxchart.data[0].chartOptions.series[0].data[0] = value;
}
}
In your component :
computed: {
...mapMutations("setBoxdata")
},
mounted() {
this.setBoxData(this.reference_fc.NSR.values);
}

How to get data from vuex state into local data for manipulation

I'm having trouble understanding how to interact with my local state from my vuex state. I have an array with multiple items inside of it that is stored in vuex state. I'm trying to get that data from my vuex state into my components local state. I have no problems fetching the data with a getter and computed property but I cannot get the same data from the computed property into local state to manipulate it. My end goal is to build pagination on this component.
I can get the data using a getters and computed properties. I feel like I should be using a lifecycle hook somewhere.
Retrieving Data
App.vue:
I'm attempting to pull the data before any components load. This seems to have no effect versus having a created lifecycle hook on the component itself.
export default {
name: "App",
components: {},
data() {
return {
//
};
},
mounted() {
this.$store.dispatch("retrieveSnippets");
}
};
State:
This is a module store/modules/snippets.js
const state = {
snippets: []
}
const mutations = {
SET_SNIPPETS(state, payload) {
state.snippets = payload;
},
}
const actions = {
retrieveSnippets(context) {
const userId = firebase.auth().currentUser.uid;
db.collection("projects")
.where("person", "==", userId)
.orderBy("title", "desc")
.onSnapshot(snap => {
let tempSnippets = [];
snap.forEach(doc => {
tempSnippets.push({
id: doc.id,
title: doc.data().title,
description: doc.data().description,
code: doc.data().code,
person: doc.data().person
});
});
context.commit("SET_SNIPPETS", tempSnippets);
});
}
}
const getters = {
getCurrentSnippet(state) {
return state.snippet;
},
Inside Component
data() {
return {
visibleSnippets: [],
}
}
computed: {
stateSnippets() {
return this.$store.getters.allSnippets;
}
}
HTML:
you can see that i'm looping through the array that is returned by stateSnippets in my html because the computed property is bound. If i remove this and try to loop through my local state, the computed property doesn't work anymore.
<v-flex xs4 md4 lg4>
<v-card v-for="snippet in stateSnippets" :key="snippet.id">
<v-card-title v-on:click="snippetDetail(snippet)">{{ snippet.title }}</v-card-title>
</v-card>
</v-flex>
My goal would be to get the array that is returned from stateSnippets into the local data property of visibleSnippets. This would allow me to build pagination and manipulate this potentially very long array into something shorter.
You can get the state into your template in many ways, and all will be reactive.
Directly In Template
<div>{{$store.state.myValue}}</div>
<div v-html='$store.state.myValue'></div>
Using computed
<div>{{myValue}}</div>
computed: {
myValue() { return this.$store.state.myValue }
}
Using the Vuex mapState helper
<div>{{myValue}}</div>
computed: {
...mapState(['myValue'])
}
You can also use getters instead of accessing the state directly.
The de-facto approach is to use mapGetters and mapState, and then access the Vuex data using the local component.
Using Composition API
<div>{{myValue}}</div>
setup() {
// You can also get state directly instead of relying on instance.
const currentInstance = getCurrentInstance()
const myValue = computed(()=>{
// Access state directly or use getter
return currentInstance.proxy.$store.state.myValue
})
// If not using Vue3 <script setup>
return {
myValue
}
}
I guess you are getting how Flux/Vuex works completely wrong. Flux and its implementation in Vuex is one way flow. So your component gets data from store via mapState or mapGetters. This is one way so then you dispatch actions form within the component that in the end commit. Commits are the only way of modifying the store state. After store state has changed, your component will immediately react to its changes with latest data in the state.
Note: if you only want the first 5 elements you just need to slice the data from the store. You can do it in 2 different ways:
1 - Create a getter.
getters: {
firstFiveSnipets: state => {
return state.snipets.slice(0, 5);
}
}
2 - Create a computed property from the mapState.
computed: {
...mapState(['allSnipets']),
firstFiveSnipets() {
return this.allSnipets.slice(0, 5);
}
}

Computed property react to localstorage change

I'm saving an array into local storage
and adding/removing from the array like.
I want the count of the array to update in the component as and when new items get added to the array in localstorage
I am using a computed property:
numOfCodes: {
// getter
get: function() {
let storageItems = localStorage.getItem("items");
if (storageItems) {
var items = JSON.parse(storageItems);
return items.length;
}
return 0;
}
}
The count is not changing as expected. it remains the same.
I have tried using vuex, but still have the issue. the goal is having the value react to the localstorage change
I think a solution to this would be to use vuex, I've mocked up an example below:
On your component:
computed: {
...mapGetters({
itemsCount: 'mockLocalStorage/itemsCount'
})
},
created() {
this.setItems(...);
},
methods: {
...mapActions({
setItems: 'mockLocalStorage/setItems'
})
}
In vuex:
state = {
items: []
};
getters = {
itemsCount: state => state.items.length
};
actions: {
setItems({ commit }, items) {
localStorage.setItem('items', items);
commit('setItems', items);
}
};
this.itemsCount would then be reactive in your component, and you could create a few more actions to add and remove individual items.
The localStorage does not share the reactivity system of Vue. This whole process is handled by Vue itself. See also here. I think you should be able to manually trigger a re-render by forcing Vue to update all of its components using forceUpdate. However, keep in mind that you would have to trigger the re-render whenever you update the localStorage or whenever you expect it to be updated.
Use a watcher.
props: ['storageItems', 'itemsLength'],
watch: {
storageItems: function(newVal, oldVal) {
this.storageItems = newVal
this.itemsLength = newVal.length
}
}

Vue: Why is this computed property not reactive?

Here is a computed getter and setter from a vue component:
paidStartHours : {
get() {
return moment(this.position.paid_start, global.DB_DATETIME).format('HH');
},
set(value) {
this.$store.commit({
type : 'updatePaidStartHours',
newValue : value,
position : this.position
});
}
}
On get, it returns the hours (HH) from position.paid_start. On set, it commits a store mutation, which essentially recreates the time string for position.paid_start.
In is bound (two-way) to an input as follows:
<input v-model="paidStartHours" type="text">
On initial load, computed property paidStartHours is correct (per Vue Tools).
When I update the input however, the value of paidStartHours does not change. I know that the commit is successful, because the props that are handed to the component are correct. It makes no sense to me.
EDIT:
The code in the updatePaidStartHours mutation has changed a number of times. For example, I tried this:
updatePaidStartHours(state, payload) {
payload.position.paid_start = 999;
}
The value of the passed prop changed to 999, however the value of the computed prop remained the same.
EDIT II:
I don't think it is worth trying to resolve this issue because I think my whole Vue / Webpack / Node installation is very sick. For example, this morning I delightedly followed this answer, Vuejs and Webpack: Why is store undefined in child components, to import an instantiated store into my App. It all seemed fine but after about 8 hours of stuffing about I see that NO store properties are reactive. I reverted to importing the config only, and now most of my store properties are reactive, but sadly not the ones above. I think I need to abandon the approach until I get time to revisit blow away my Vue / Webpack / Node install and start again.
Vuex mutations should be called from Vuex actions only - so in your code you should dispatch an action, not a mutation. Also, your mutation function is expected to mutate the store argument - not the payload one. Your getter is also wrong - it should use this.$store.getters and not your local component data.
paidStartHours : {
get() {
return moment(this.$store.getters.position.paid_start, global.DB_DATETIME).format('HH');
},
set(value) {
this.$store.dispatch('updatePaidStartHours',value);
}
}
Vuex module:
// initial state
const state = {
position:
{
paid_start: null
}
};
// getters
const getters = {
position: (state) => state.position
}
// actions
const actions = {
updatePaidStartHours ({commit}, payload)
{
commit('SET_START_HOURS', payload);
}
}
// mutations
const mutations = {
SET_START_HOURS (state, payload)
{
state.position.paid_start = payload;
}
}