How to return promise result from Vuex action instead of the promise itself? - vue.js

I have an action in my vuex store:
export const actions = {
myaction() {
return 'foo'
}
}
Can I get the promise result (here foo) in the mounted() life cycle hook and display it in the console ? If yes, how ?
I tried this:
mounted() {
console.log(
this.$store
.dispatch('myaction')
.then(res => res)
)
}
But it returns the promise instead of the promise result I'm expecting.

Either of these should work:
Using .then():
mounted() {
this.$store
.dispatch('myaction')
.then(res => console.log(res));
}
Or if you're using ES2017 or later (or some compatibility tool like Babel), then you can use async/ await:
async mounted() {
const res = await this.$store.dispatch('myaction');
console.log(res);
}
As #Dan mentions below, whilst this will return you the value of the Promise, this is not the intended usage of Vuex, which prefers all data to be saved to and accessed from Vuex's state.
It would be better to mutate the store in your action to save the value and then use a computed property in your component to retrieve it from state, either directly or through a getter. This can be done very cleanly with the mapGetters Vuex helper function.

Related

Nuxt store getter not working, ID given to payload is not an Integer + Error: [vuex] do not mutate vuex store state outside mutation handlers

I am trying to make a product detail page. The detail page is named _id.
When opened the id is replaced with the product id. On opening the page the state is set with data fetched from an api.
After that i am trying to use a computed property that refers to a getter named getProduct() with an id (this.$route.params.id) in the payload.
This is how my _id.vue looks like:
methods: {
...mapActions("products", ["fetchProducts",]),
...mapGetters("products", ["getProduct",]),
},
async mounted() {
this.fetchProducts()
},
computed: {
product() {
return this.getProduct(this.$route.params.id)
}
}
This is how my store file named products.js looks like:
import axios from "axios"
export const state = () => ({
producten: []
})
export const mutations = {
setProducts(state, data) {
state.producten = data
}
}
export const getters = {
getProduct(state, id) {
console.log(id)
return state.producten.filter(product => product.id = id)
}
}
export const actions = {
async fetchProducts({ commit }) {
await axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
What works is creating the state, but when i try to use the getter something goes wrong.
As you can see i console.log() the id given to it. Which logs the following:
I also get the error: client.js?06a0:103 Error: [vuex] do not mutate vuex store state outside mutation handlers.
Which I'm not doing as far as I know?
**Note: **these errors get logged as much as the length of my state array is.
From the Vuex documentation:
Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
Like computed, getters does not support having arguments.
But there is a way to have "method-style access" to a getter: https://vuex.vuejs.org/guide/getters.html#property-style-access
You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Note that getters accessed via methods will run each time you call them, and the result is not cached.

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.

Returning a getters in a computed create a loop

I am calling inside the computed an action from the store to run it and after I am returning a getter, this will create a loop.
The HTML
{{loadedProjects}}
The computed
computed: {
loadedProjects() {
this.$store.dispatch("getProjects");
return this.$store.getters.loadedProjects;
}
}
The store
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
loadedProjects: []
},
mutations: {
setProjects(state, projects) {
state.loadedProjects = projects
}
},
actions: {
getProjects(vuexContext) {
console.log("hello1")
return axios.get("THE API URL")
.then(res => {
console.log("hello2")
vuexContext.commit("setProjects", res.data);
})
.catch(e => console.log(e));
}
},
getters: {
loadedProjects(state) {
return state.loadedProjects;
}
}
});
};
export default createStore;
I expect to call my action to populate my state and after to return my state to render my data.
What is the point of using the store action that makes an API call inside the computed property ... maybe you want to trigger loadedProjects change ? ....computed property is not asynchronous so either way the return line will be executed before the you get the response... you might try vue-async-computed plugin OR just use the call on the created hook like you have done which is the better way and you don't have to use a computed property you can just {{ $store.getters.loadedProjects }} on your template
Computed properties should not have side effects (e.g. calling a store action, changing data, and so on). Otherwise it can happen that the triggered side effect could lead to a re-rendering of the component and possible re-fetching of the computed property. Thus, an infinite loop
I changed the code like that:
created: function () {
this.$store.dispatch("getProjects")
},
computed: {
loadedProjects() {
return this.$store.getters.loadedProjects
}
}
It is working now but I would like to know but I have that problem working inside the computed and also I wonder if it's the best solution. Any help????

Vuex returning some data back the the component

I have an axios call in my vuex actions
return axios({
method: 'get',
url: `/myurl`,
}).then(function (response) {
context.commit('value', response.data.data);
}),
However this is called in my component
this.$store.dispatch("fetchmystuff")
How do I return a value to the component?
In the past I have attached the then() to the dispatch
this.$store.dispatch("fetchmystuff")
.then(function (response) {
//do some component stuff
}),
but I would like to run the commit first in vuex, then return something to the component.
You've dispatched the action fetchmystuff.
From within your component, you will want to either.
1. query the store for the state of value
computed: {
value() {
return this.$store.state.value
}
}
2. call a getter which gets the state of value from a computed property
in component
computed: {
value() {
return this.$store.getters.value
}
}
in store getters.js
getters: {
// ...
value: (state, getters) => {
return getters.value
}
}
The dispatcher/action shouldn't need to access to the component
(as state is only set in store via mutations/commits, and have state passed to other components via getters).
This allows a decoupling of concerns between the store and the component parts of your application.

computed property not reading data initialized in created

I am not sure when computed property (in vue lifecycle) comes. Let's say I have a method that I run in created() as:
created () {
getSomething()
}
Inside getSomething() I fetch data and fill my data property as:
getSomething(){
axios.get(...) { this.info = response.data }
}
Now, in computed properties, I do:
computed: {
doSomething () {
this.info.forEach(item => {})
}
}
But, inside my computed I get forEach is undefined, as if this.info is not an array or has not be filled.
What am I doing wrong? are computed props called before created() or something?
try something like this
getSomething(){
return axios.get(...) { this.info = response.data }
}
then you can use the promise returned in created method like this....
created () {
getSomething().then( () => {
doSomething()
}}
}
You could utilise Vuex' state management...
Vuex has :
Dispatches
Actions
Mutations
Getters
What i am thinking is, on your page load, map an action to the store that makes a call to your axios service and after it returns, commit a mutation that stores the response to the store...
then you can map the state to your component and retrieve the data.
This might not be the quickest answer but if you are expecting to need the response data from the axios request, it might be worth putting it into some sort of state management :-)