V-for Vuex do not mutate vuex store state outside mutation handlers - vue.js

I'm getting an error with my v-for
[vuex] do not mutate vuex store state outside mutation handlers.
An error occurred while rendering the page. Check developer tools console for details.
Here is the class that is throwing the error.
<b-row>
<b-col v-for="favorite in favorites" :key="favorite.id">
<FavoriteCard :favoriteId="favorite" />
</b-col>
</b-row>
...
export default Vue.extend({
async mounted() {
if (this.isAuthenticated) {
await this.$store.dispatch("myStore/getAllFavorites");
}
},
computed: {
favorites: function() {
let favorites = this.$store.getters['myStore/getFavorites'];
return favorites;
}
}
})
And FavoriteCard.vue
<template>
<b-card #>
<nuxt-link :to="this.favoriteId.link" :id="'favorite' + this.favoriteId.id">{{this.favoriteId.title}}</nuxt-link>
</b-card>
</template>
<script lang="ts">
import Vue, { PropOptions } from 'vue';
export default {
name: "FavoriteCard",
props: {
favoriteId: {
type:Object,
required:true
}
}
};
And my store
const state = () => ({
favorites: []
})
const mutations = {
setFavorites (state, favorites) {
state.favorites = favorites
},
createNewFavorite (state, favorite, isMonograph) {
state.favorites.push(favorite, isMonograph);
},
deleteFavorite (state, favorite) {
let index = state.favorites.map(function(f) {return f.id}).indexOf(favorite.id);
state.favorites.splice(index, 1);
},
}
const getters = {
getFavorites: (state) => {
return state.favorites;
},
}
const actions = {
async getAllFavorites ({ commit }) {
let { data } = await this.$axios.get(`/favorites`);
commit('setFavorites', data);
},
}
When I try to update the store, I get an error that I shouldn't mutate the vuex store state outside of mutation handlers. What should I do to fix this error?
Thanks!

Related

Vue 3 - Composition API fetching data?

I am a bit confused with composition API and fetching data. When I open the page, I can see rendered list of categories, but if I want to use categories in setup(), it is undefined. How can I use categories value inside setup function? You can see that I want to console log categories.
Category.vue
<template>
<div class="page-container">
<item
v-for="(category, index) in categories"
:key="index"
:item="category"
:is-selected="selectedItem === index"
#click="selectItem(index)"
/>
</div>
</template>
<script>
import { computed, ref } from 'vue'
import { useStore } from 'vuex'
import Item from '#/components/Item.vue'
export default {
components: {
Item
},
setup () {
const store = useStore()
store.dispatch('categories/getCategories')
const categories = computed(() => store.getters['categories/getCategories'])
const selectedItem = ref(1)
const selectItem = (index) => {
selectedItem.value = index
}
console.log(categories.value[selectedItem.value].id)
return {
categories,
selectedItem,
selectItem
}
}
}
</script>
<style lang="scss" scoped>
#import '#/assets/scss/general.scss';
</style>
categories.js - vuex module
import axios from 'axios'
import { API_URL } from '#/helpers/helpers'
export const categories = {
namespaced: true,
state: {
categories: []
},
getters: {
getCategories: (state) => state.categories
},
mutations: {
UPDATE_CATEGORIES: (state, newValue) => { state.categories = newValue }
},
actions: {
async getCategories ({ commit }) {
await axios.get(`${API_URL}/getCategories.php`).then(response => {
commit('UPDATE_CATEGORIES', response.data.res_data.categories)
})
}
},
modules: {
}
}
In the setup function you cannot process a computed function.
You can instead access store.getters['categories/getCategories'].value[selectedItem.value].id if you want to process that in the setup function.

Vue/Vuex: mapState inside computed is not updating

I am trying to make use of mapState and running into issues with reactive data. I have the following inside my Test.vue component
<template>
<div> {{ name }} </div>
</template>
computed: {
...mapState('user', ['age','name]
}
when my state user.name updates outside of the Test.vue component, the new value is not showing inside Test.vue.
so for example, if I have an update via a mutation in my userStore,
[SET_USER_NAME_MUTATION](state, value) {
state.name = value;
},
commit('SET_USER_NAME_MUTATION', "John")
now in my Vuex store when I check chrome DevTools , user { name: "John" } , which is correct
You should mutate state through vuex actions instead of directly calling the mutation.
Try with something like this, assuming your state contains a user object with name property:
Vue component
<template>
<div>
<span>{{ name }}</span>
<button #click="changeName">Change name</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'MyComponent',
computed: {
...mapState({
name: state => state.user.name
})
},
methods: {
changeName () {
this.$store.dispatch('changeName', 'John Smith')
}
}
}
</script>
Vuex store
// state
const state = {
user: {
name: null
}
}
// getters
const getters = {
// ...
}
// actions
const actions = {
changeName ({ commit }, payload) {
commit('setName', payload)
}
}
// mutations
const mutations = {
setName (state, payload) {
state.user.name = payload
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Anyway, it will be very helpful to know your state structure to a better approach as per your specific case

Vue received a Component which was made a reactive object

The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>

Vuex sync : Cannot read property of undefined

Im trying to use vuex to make things easier, overall it's fine, but Im stuck when using a getter with param from an other getter.
main code :
<template>
<v-container>
<v-card v-for="(order,i) in getOrders" :key="i" class="cart-cards text-left">
<v-card-title>
{{getMealById(order.meal_id).name}}
</v-card-title>
<v-btn v-on:click="addQuantity(order)">
+
</v-btn>
<h1>
{{order.quantity}}
</h1>
<v-btn #click="reduceQuantity(order)">
-
</v-btn>
</v-card>
</v-container>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data: () => ({
}),
created() {
this.fetchOrders();
},
mounted() {
},
methods: {
...mapActions(["fetchOrders"]),
addQuantity(order) {
order.quantity += 1;
this.updateOrders(order);
},
reduceQuantity(order) {
if (order.quantity > 0) {
order.quantity -= 1;
this.updateOrders(order);
}
},
},
computed: {
...mapGetters(["getOrders", "getMealById"]),
},
};
order.js :
import axios from 'axios'
import url from '../../config.js'
const state = {
all_orders: [],
}
const getters = {
getOrders : (state)=>state.all_orders,
}
const actions = {
async fetchOrders({commit}) {
const response = await axios.get("http://" + url + "/orders")
commit('setOrders',response.data)
},
async updateOrders({commit},payload) {
const response = await axios.put("http://" + url + "/orders/"+payload.id,payload)
commit('setOrders',response.data)
},
}
const mutations = {
setOrders: (state,orders)=>{
state.all_orders = orders
},
}
export default {
state,
getters,
actions,
mutations
}
meal.js
import axios from 'axios'
import url from '../../config.js'
const state = {
all_meals: [],
}
const getters = {
getMeals: (state) => state.all_meals,
getMealById: (state) => (id) => {
return state.all_meals.find(todo => todo.id === id)
}
}
const actions = {
async fetchMeals({ commit }) {
const response = await axios.get("http://" + url + "/meals")
commit('setMeals', response.data)
},
}
const mutations = {
setMeals: (state, meals) => {
state.all_meals = meals
},
}
export default {
state,
getters,
actions,
mutations
}
So when iam accessing the vue from a link, no error, but when I load the url by itself, an error occur and the getMealById dont trigger
overall Is their a good practice for "waiting" for response on state/actions call ?
Thanks in advance !!!
In component, you can check if getMeals returns a non-empty array, then render the v-for loop:
<template>
<v-container v-if="getMeals().length > 0">
<v-card v-for="(order,i) in getOrders" :key="i" class="cart-cards text-left">
<v-card-title>
{{getMealById(order.meal_id).name}}
</v-card-title>
</v-card>
</v-container>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data: () => ({
}),
created() {
this.fetchOrders();
},
mounted() {
},
methods: {
...mapActions(["fetchOrders"]),
},
computed: {
...mapGetters(["getMeals", "getOrders", "getMealById"]),
},
};

async vuex fetch action state filled if using variable in template getting error undefined

i have one async action vuex, im using map getters and component created function to fetch and fill data, if im using this store data inline object in template view console show error undefined, if i try acces variable only without inline object im getting undefined error for inline object, i think this error about async function not blocking main process component fully loaded and after async function filled variable
actions, state
// state
export const state = {
app: null
}
// getters
export const getters = {
app: state => state.app,
}
// mutations
export const mutations = {
[types.FETCH_APP_SUCCESS] (state, { app }) {
state.app = app
},
[types.FETCH_APP_FAILURE] (state) {
state.app = null
},
[types.UPDATE_APP] (state, { app }) {
state.app = app
}
}
async fetchApp ({ commit }) {
try {
const { data } = await axios.get('/api/app/1')
commit(types.FETCH_APP_SUCCESS, { app: data })
} catch (e) {
commit(types.FETCH_APP_FAILURE)
}
}
component
<template>
<div>
{{app.name}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
created () {
// i try here async and await
this.$store.dispatch('app/fetchApp')
},
computed: mapGetters({
app: 'app/app'
}),
metaInfo () {
return { title: this.$t('home') }
}
}
</script>
state is filled
variable can see in html
but console this error
app/app is initially null, and your template does not have a null check on app.name, which results in the error you saw. You can either conditionally render app.name in the template:
<template>
<div>
<template v-if="app">
{{app.name}}
</template>
</div>
</template>
Or use the empty string as app/app's initial state instead of null in your store.