do not mutate vuex store state outside mutation handlers - Vuetify snackbar - vuex

I have a snackbar from Vuetify. It's in default.vue and the vuex store controls the v-model, message and color:
DefaultSnackBar.vue
<template>
<v-container>
<v-snackbar
v-model="snackbarProperties.show"
:color="snackbarProperties.color"
timeout="7000"
multi-line
>
{{ snackbarProperties.message }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
#click="hideSnackbar"
>
Close
</v-btn>
</template>
</v-snackbar>
</v-container>
</template>
<script>
import { mapActions } from "vuex";
import { mapGetters } from "vuex";
export default {
methods :{
...mapActions("Snackbar",["showSnackbar","hideSnackbar"]),
},
computed: {
...mapGetters("Snackbar",["snackbarProperties"])
},
}
</script>
Snackbar.js
export const state = () => ({
message: "",
color: "",
show: false,
});
export const getters = {
snackbarProperties: state => {
return state;
},
}
export const mutations = {
showSnackbar: (state, payload) => {
state.message = payload.message;
state.color = payload.color;
state.show = true;
},
hideSnackbar: (state) => {
state.message = "";
state.color = ""
state.show = false;
},
}
export const actions = {
showSnackbar({ commit }, payload) {
commit('showSnackbar', payload)
},
hideSnackbar({ commit }) {
commit('hideSnackbar')
}
}
When I call showSnackbar({...}) the bar appears correctly with no errors, but when it disappears (timeout is reached) is get this error and everything crashes
do not mutate vuex store state outside mutation handlers
I think it's because when the bar disappears the component changes the value of the v-model it's attached to but I'm not sure how to work around this.

I found the answer from this vue forum:
Use an action with the setTimeout code in it. Then in the timeout
commit the mutation. Mutations should be synchronous which is why
using a timeout in them is throwing a warning.
I've updated Snackbar.js to suit:
export const state = () => ({
message: "",
color: "",
show: false,
});
export const getters = {
snackbarProperties: state => {
return state;
},
}
export const mutations = {
showSnackbar: (state, payload) => {
state.message = payload.message;
state.color = payload.color;
state.show = true;
},
hideSnackbar: (state) => {
state.message = "";
state.color = ""
state.show = false;
},
}
export const actions = {
showSnackbar({ commit }, payload) {
commit('showSnackbar', payload)
setTimeout(() => {
commit('hideSnackbar')
}, 500);
},
hideSnackbar({ commit }) {
commit('hideSnackbar')
}
}

try this if you need showing multiple
<template>
<div class="text-center">
<v-snackbar
v-for="(snackbar, index) in snackbars.snackbars.filter(
(s) => s.isVisible
)"
:key="snackbar.text + Math.random()"
v-model="snackbar.isVisible"
:color="snackbar.color"
:timeout="-1"
:right="true"
:top="true"
:style="`top: ${index * 60}px`"
>
<v-row no-gutters>
<v-col md="11" sm="11">
{{ snackbar.text }}
</v-col>
<v-col md="1" sm="1">
<v-btn class="mx-2" icon small #click="hideNotify(index)">
<v-icon color="error"> mdi-close </v-icon>
</v-btn>
</v-col>
</v-row>
</v-snackbar>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({ snackbars: 'notification' }),
},
methods: {
hideNotify(index) {
this.$store.dispatch('notification/HIDE_NOTIFY_WITH_INDEX',index)
},
},
}
</script>
add this into the vuex as notification.js
export const state = () => ({
snackbars: [],
})
export const mutations = {
SET_SNACKBAR(state, snackbar) {
state.snackbars = state.snackbars.concat(snackbar)
},
HIDE_NOTIFY_WITH_INDEX(state,index) {
if (index in state.snackbars) {
state.snackbars.splice(index, 1)
}
},
HIDE_NOTIFY(state) {
state.snackbars = []
},
}
export const actions = {
SET_SNACKBAR({ commit }, snackbar) {
snackbar.isVisible = true
snackbar.color = snackbar.color || 'dark'
commit('SET_SNACKBAR', snackbar)
setTimeout(() => {
commit('HIDE_NOTIFY')
}, 6000)
},
HIDE_NOTIFY_WITH_INDEX({ commit },index) {
commit('HIDE_NOTIFY_WITH_INDEX',index)
},
}

Related

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

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!

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"]),
},
};

nuxtServerInit data not show in page

I try to use nuxtServerInit method.
index.js
import productsService from "../services/productsService";
export const state = () => ({
hotDeals: [],
specialities: []
})
export const mutations = {
SET_SPECIALITIES(state, payload) {
state.specialities = payload;
}
}
export const actions = {
async nuxtServerInit({ dispatch}, ctx) {
try {
await dispatch('fetchSpecialities');
}catch (e) {
console.log(e);
}
},
fetchSpecialities({ commit }) {
productsService.getSpecialities()
.then(response => {
commit('SET_SPECIALITIES', response.data);
});
}
}
component usage
<template>
<v-layout
justify-center
align-center
>
<div>
<v-row >
<span v-for="item in specialities">{{item.productCode}}</span>
</v-row>
</div>
</v-layout>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState(["specialities"])
}
}
</script>
But it show nonthing on page. If I try to use console.log(state.specialities) in mutation after change state I can see data in web storm console. But in component data is not showing.
i think using watchers will solve your problem
watch: {
specialities(newValue, oldValue) {
console.log(`Updating from ${oldValue} to ${newValue}`);
},
},

Vuex getter always return null

When i use vuex getter in my vue.js component it return null for me.
Here is my code
MainLayout.vue
<script>
import NavBar from '#/components/NavBar.vue'
import ToolBar from "#/components/ToolBar"
import { mapActions, mapGetters } from 'vuex'
export default {
name: "MainLayout",
components : {
ToolBar, NavBar
},
data: () => ({
drawer: null,
}),
computed: {
...mapGetters([
'error',
]),
},
methods: {
close() {
this.$store.commit('SET_ERROR', null)
},
}
}
</script>
<template>
<div id="main">
<v-navigation-drawer clipped v-model="drawer" app>
<nav-bar></nav-bar>
</v-navigation-drawer>
<tool-bar #toggleDrawer="drawer = !drawer"/>
<v-content>
<v-container class="fill-height" fluid>
<router-view></router-view>
</v-container>
</v-content>
<v-snackbar :timeout="0" :value="error">
{{ error }}
<v-btn color="red" text #click="close">
Close
</v-btn>
</v-snackbar>
</div>
</template>
<style scoped>
</style>
Here is NavBar.vue
<script>
import { mapGetters } from 'vuex'
export default {
data: () => ({
}),
computed: {
...mapGetters([
'authUser'
]),
isAdmin() {
return this.authUser.role.name == 'admin'
},
}
}
</script>
Vuex module auth.js
import api from '#/api'
import {clearAccessToken, setAccessToken} from '#/auth'
import router from '#/router'
const state = {
loading: null,
user: null
}
const mutations = {
SET_LOADING: (state, loading) => {
state.loading = loading
},
SET_USER: (state, user) => {
state.user = user
}
}
const getters = {
loading: state => {
return state.loading
},
loggedIn: (state) => {
return !!state.user
},
authUser: (state) => {
return state.user
},
}
const actions = {
async login({commit, dispatch }, user) {
commit('SET_LOADING', true)
try {
const data = await api.post('/api/auth/login', { user })
setAccessToken(data.token)
await dispatch('getUser')
commit('SET_LOADING', false)
router.push('/')
} catch (e) {
commit('SET_LOADING', false)
dispatch('handleError', e)
}
},
async getUser({commit, dispatch}) {
try {
const user = await api.get('/api/auth/user')
commit('SET_USER', user.data)
return user
} catch (e) {
clearAccessToken()
dispatch('handleError', e)
}
},
async logout({commit, dispatch}) {
try {
await api.post('/api/auth/logout')
clearAccessToken()
router.push('/login')
} catch(e) {
dispatch('handleError', e)
}
}
}
export default {
namespaced: false,
state,
getters,
actions,
mutations,
}
When i run this code i have next error
[Vue warn]: Error in render: "TypeError: Cannot read property 'role' of null"
But if i add code
isAdmin() {
return this.authUser.role.name == 'admin'
},
in ToolBaar component (and remove from NavBar)
<script>
import { mapGetters } from 'vuex'
export default {
methods: {
toggleDrawer () {
this.$emit('toggleDrawer')
},
logout() {
this.$store.dispatch('logout')
}
},
computed: {
...mapGetters([
'loggedIn',
'authUser'
]),
fullName() {
return this.authUser.first_name + ' ' + this.authUser.last_name
},
isAdmin() {
return this.authUser.role.name == 'admin'
}
},
}
</script>
Then it work good, without any error, so i dont know what is the issue here, in one component code work good, and in another it doesnt, also if i add it in MainLayout component and pass isAdmin as props then it also work. Help me pls fix this.
Also, i dispatch user in router hook
router.beforeEach(async(to, from, next) => {
const needAuth = to.matched.some(record => record.meta.auth)
function redirectToLogin() {
next({
path: '/login',
query: { redirect: to.fullPath },
})
}
if (!hasToken() && needAuth) {
return redirectToLogin()
}
if (hasToken() && !store.getters.loggedIn) {
try {
const user = await store.dispatch('getUser')
if (!user) {
return redirectToLogin()
}
} catch(e) {}
}
next()
})
You should guard your access of authUser with loggedIn. For example
isAdmin() {
return this.loggedIn && this.authUser.role.name == 'admin'
}

Getting Error in render: "TypeError: Cannot read property 'title' of undefined" when rendering CourseDescriptionPageComponent

Here is how CourseDescriptionPage.vue looks
import CourseCover from './CourseDescription/CourseCover.vue'
import WhyJoin from './CourseDescription/WhyJoin.vue'
import CourseStructure from './CourseDescription/CourseStructure.vue'
export default {
props: ['id'],
data () {
return {
hasDetails: false
}
},
created () {
this.$store.dispatch('loadCourseDetails', this.id).then(() => {
this.hasDetails = true
})
},
computed: {
course () {
return this.$store.state.courseDetails[this.id]
}
},
components: {
CourseCover,
WhyJoin,
CourseStructure
},
name: 'CourseDescriptionPage'
}
<template>
<div v-if="hasDetails">
<course-cover :courseTitle="course.title" :courseDuration="course.duration"></course-cover>
<why-join :courseTitle="course.title" :courseJobs="course.jobs"></why-join>
<course-structure :lectureList="course.lectureList"></course-structure>
</div>
</template>
Here is how my store looks
import Vuex from 'vuex'
import * as firebase from 'firebase'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
courseDetails: {},
loading: false
},
mutations: {
setCourseDetails (state, payload) {
const { id, data } = payload
state.courseDetails[id] = data
},
setLoading (state, payload) {
state.loading = payload
}
},
actions: {
loadCourseDetails ({commit}, payload) {
commit('setLoading', true)
firebase.database().ref(`/courseStructure/${payload}`).once('value')
.then((data) => {
commit('setCourseDetails', {
id: payload,
data: data.val()
})
commit('setLoading', false)
})
.catch(
(error) => {
console.log(error)
commit('setLoading', false)
}
)
}
}
Here is how my CourseCover.vue looks
export default {
props: {
courseTitle: {
type: String,
required: true
},
courseDuration: {
type: String,
required: true
}
},
name: 'CourseCover'
}
<template>
<v-jumbotron
src="./../../../static/img/course_cover_background.png">
<v-container fill-height>
<v-layout align-center>
<v-flex>
<h3>{{ courseTitle }}</h3>
<span>{{ courseDuration }}</span>
<v-divider class="my-3"></v-divider>
<v-btn large color="primary" class="mx-0" #click="">Enroll</v-btn>
</v-flex>
</v-layout>
</v-container>
</v-jumbotron>
</template>
I think there is something wrong with the way I am using props here but I couldn't figure out.
The data is loaded in store by the firebase that I know for sure because it shows in Vue dev tools but I just couldn't understand why Vue is complaining about that.
Thanks in advance.
course is undefined on component initialize ,so then you should return an empty object:
computed: {
course () {
return this.$store.state.courseDetails[this.id] || {}
}
},