Vuex: How to replace function emitting between parent and child with Vuex? - vue.js

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

Related

Updating getter value Vuex store when state changes

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.

How to watch for vuex state?

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.

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);
}
}

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;
}
}

Vuejs simplify vuex action call

I am calling a store action through a component like:
sublitPost(){
this.$store.dispatch({type:"submitPost", userPost:this.newPost});
}
and the store action looks like this:
actions: {
submitPost({commit, state}, userPost: any) {
/...rest code here
Although, I would like to simplify this and call the action like:
sublitPost(){
this.$store.dispatch("submitPost", this.newPost);
}
What is the tweak in the action's signature I have to do? What I've tried is to have the action like:
actions: {
submitPost(userPost: any) {
console.log({userPost})
/...rest code here
but in this case, the received object doesn't look correct. What I get in the console log is:
Any help is welcome
You should change your syntax:
actions: {
submitPost(state, data) {
console.log(data)
{
I have divided my store into modules so I am exporting my all mutations, actions, getters.
I am committing a mutation from my action with payload,here payload is an object with property currentWidth
export function fetchDeviceCurrentWidth ({ commit, state }, payload) {
commit('updateDeviceCurrentWidth', payload)
}
My mutation
export function updateDeviceCurrentWidth (state, payload) {
state.currentDeviceWidth = payload.currentWidth
}
My getter
export const currentDeviceWidth = state => state.currentDeviceWidth
This is from where I am dispatching an action:
myEventHandler () {
this.$store.dispatch('fetchDeviceCurrentWidth', {
currentWidth: window.innerWidth
})
}
There are many ways to use vuex store, but I like to break store into modules depending on features or Components.