I am working on vuex app, on my other page i am committing changes to state its working, but here on this specific page i am getting some data from api and storing it in store but it get stuck in mutation, I am getting all data in mutation payload but its not effecting the changes, Please check the screenshot and code,
I can not create fiddle cuz it works there
Getting Items
async getItems () {
await this.$axios.get(`/api/projects/w/latest/all`)
.then(response => {
this.$store.commit('project/UPDATE_PROJECTS', response.data.items)
});
}
Action
updateProjectsAction (context, projects) {
context.commit('UPDATE_PROJECTS', projects)
},
Mutation
UPDATE_PROJECTS (state, payload ) {
state.projects = payload
}
State
projects: {},
Response
When i click load state or manually commit these changes it gives me this error.
In getting items
this.$store.commit('project/UPDATE_PROJECTS', response.data.items)
you should change it to
this.$store.dispatch('project/updateProjectsAction', response.data.items)
In Action you should change
updateProjectsAction ({commit}, projects) {
commit('UPDATE_PROJECTS', projects)},
And last thing, you should have getters for getting data from vuex
getters:{
getProjects: state =>{
return state.project
}
}
In .vue
import 'mapGetters' from 'vuex'
export default {
computed:{
...mapGetters({
projects: project/getProjects
})
}
}
getters and computed will helps your vue app reactive
Related
I'm trying to update a state and i get this error: Error: [vuex] do not mutate vuex store state outside mutation handlers.. The following creates state for categories.
register.vue , i can see the state categories in vuex is updated.
async saveCategories(){
if(this.selectedCategories.length) {
this.$store.dispatch('app/Categories', this.selectedCategories);
this.$router.push(`/dashboard`);
}
}
dashboard.vue, when i try to update the state with new selectedCategories in method updateCategories, the state categories isn't updated when i check vuex in chrome extension
async mounted() {
if(this.$store.state.app.Categories.length) {
this.selectedCategories = this.$store.state.app.Categories.map((category: any) => {
return parseInt(category);
});
}
}
updateCategories() {
this.$store.dispatch('app/Categories', this.selectedCategories);
}
I get this error Error: [vuex] do not mutate vuex store state outside mutation handlers.
this is how i access the state this.$store.state.app.Categories
How do i solve?
This happens because you can do an action in store instead of in a component.
this.$store.state.app.Categories.map((category: any) => parseInt(category));
The solution is to create a mutation for this and instead call that mutation in the component.
state: {
app: {
Categories: []
}
}
mutations: {
toInt(state) {
state.app.Categories.map((category: any) => parseInt(category))
}
}
Register.vue
async mounted() {
this.$store.commit('toInt')
}
I have a vue 3 app with vuex, below is my store :
import VueX from 'vuex'
import ProductService from '#/services/ProductService'
export default new Vuex.Store({
state:{ products : [] },
getters : {
getProductFromSlug(state){
console.log(state) // <= state with products retrieved (please see first screen capture below)
console.log(state.products) // <== empty ??? (please see second screen capture)
return state.products.filter( product => {
return product.slug == 'my-slug'
})
}
},
actions:{
fetchProducts(context){
ProductService.getProducts().then( response => context.commit('setProducts', response.data) )
}
},
mutations: { setProducts(state, products) { this.state.products = products } }
})
From my component, i want to retrieve a product by slug :
...
created() {
store.dispatch('fetchProducts')
console.log(store.getters.productFromSlug)
}
First output of state ==>
Output of state.products ==>
Anyone can explain me please why i get empty values from the getProductFromSlug ?
Console output shouldn't be used as a reliable way of debugging. Here this problem is observed, an array is passed by reference to console, it doesn't reflect the current state of things.
The mistake here is that promises aren't chained correctly, this results in race condition. dispatch always returns a promise, asynchronous actions should always return a promise. Then it's not waited. It should be:
async created() {
await store.dispatch('fetchProducts')
console.log(store.getters.productFromSlug)
}
and
fetchProducts(context){
return ProductService.getProducts().then(...)
}
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.
I have an array. I take data for it via rest api. I can call a mutation getData() from any component, but I need it to be automatically called when an object Vuex.Store is created, can I do this?
export default new Vuex.Store({
state: {
myArray: [],
},
mutations: {
getData() {
//get data from remote API and pass to myArray
axios.post('').then(response => {
this.myArray = response.data;
};
}
}
})
First things first: Mutations are synchronous pure functions. This means that your mutations should not have side-effects, and at the end of your mutation the state of your store should be updated to the new state. Axios uses promises and is thus asynchronous. You should do this in an action!
As for automatically executing a data fetch, you can either do this in the file where you define your store, or in your Vue entry point (e.g. App.vue) in a lifecycle hook. Keep in mind though that your axios call is asynchronous, which means that your app will load while data is loading in the background. You have to handle this case somehow.
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
const store = new Vuex.Store({
state: {
myArray: [],
},
mutations: {
setMyArray(state, payload) {
Vue.set(state, 'myArray', payload);
},
},
actions: {
fetchData({ commit }) {
axios.post('').then(response => {
commit('setMyArray', response.data);
};
}
}
});
// Setup
Vue.use(Vuex);
// Now that we have a store, we can literally just call actions like we normally would within Vue
store.dispatch('fetchData');
// Keep in mind that action is not blocking execution. Execution will continue while data is fetching in the background
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.