Vue/Vuex: mapState inside computed is not updating - vue.js

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

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!

unable to retrieve mutated data from getter

I'm trying to render a d3 graph using stored data in vuex. but I'm not getting data in renderGraph() function.
how to get data in renderGraph()?
Following is store methods.
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import * as d3 from "d3";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
subscribers: []
},
getters: {
getterSubscribers: state => {
return state.subscribers;
}
},
mutations: {
mutationSubscribersData: (state, payload) => {
state.subscribers = payload;
}
},
actions: {
actionSubscribersData: async ({ commit }) => {
let subsData = await d3.json("./data/subscribers.json"); // some json fetching
commit("mutationSubscribersData", subsData);
}
}
});
Below is parent component
Home.vue
<template>
<div>
<MyGraph /> // child component rendering
</div>
</template>
<script>
import MyGraph from "./MyGraph.vue";
export default {
components: {
MyGraph
},
};
</script>
Below is child component.
MyGraph.vue
<template>
<div>
<svg width="500" height="400" />
</div>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
computed: {
...mapGetters(["getterSubscribers"])
},
methods: {
...mapActions(["actionSubscribersData"]),
renderGraph(data) {
console.log(data); // DATA NOT COMING HERE
// MyGraph TO BE RENDERED WITH THIS DATA
}
},
mounted() {
this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
};
</script>
I have tried mounted, created lifecycle hooks. but did not find data coming.
There is race condition. actionSubscribersData is async and returns a promise. It should be waited for until data becomes available:
async mounted() {
await this.actionSubscribersData();
this.renderGraph(this.getterSubscribers);
}
There must be delay for the actionSubscribersData action to set value to store. Better you make the action async or watch the getter. Watching the getter value can be done as follows
watch:{
getterSubscribers:{ // watch the data to set
deep:true,
handler:function(value){
if(value){ // once the data is set trigger the function
this.renderGraph(value);
}
}
}
}

State variable triggers error when displaying it in template as it's temporarily null when component mounts

My user state variable is an object having several properties such as first_name. I want to display some of these properties in my component template.
I'm assign my state variable to a computed property which I use in template thus:
<template>
<div>
{{ user.first_name }}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState({
user: state => state.dashboard.user
})
},
beforeMount () {
this.$store.dispatch("dashboard/getUser");
}
};
</script>
Although it works, I get the following error in console:
Error in render: "TypeError: Cannot read property 'title' of null"
I suppose it's because user is null for a split second as component mounts, till it receives info that Vue correctly displays in template. How to avoid the error though?
[EDIT] here are the relevant part of the store:
state: {
user: null
},
...
actions: {
async getUser({ commit }) {
let user = await axios.get(`user`).catch(console.error);
commit("SET_USER", user);
return user;
}
},
In your mapped getter you could default to an empty object like
state => state.dashboard.user || {}
That way things like user.first_name would be undefined rather than attempting to look for a property on the value null
Ok. I've rewritten the code.
store.js
state: {
user: ''
},
mutations: {
SET_USER: (state, user) => {
state.user = user
}
},
actions: {
getUser: (context, user) => {
axios.get('url/to/server')
.then(res => {
context.commit('SET_USER', res.data)
})
.catch(error => {
console.log(error)
})
}
}
Now in your root component (App.vue for example)
import {mapActions} from 'vuex'
export default{
...
mounted() {
this.getUser()
},
methods: {
...mapActions(['getUser'])
}
}
In the component, you wish to use the user data
<template>
<div>
{{user.first_name}}
</div>
<template/>
import {mapState} from 'vuex'
export default{
computed: {
...mapState(['user'])
}
}
This will work.
Hope it helps.

Nuxt.js unknown store

I'm new to VueJS and try to use the store with NuxtJS.
So, i want to get this (https://nuxtjs.org/guide/vuex-store/) to work.
My store ./store/index.js
import { mapActions, mapGetters } from 'vuex'
export const state = () => ({
temperature: '1234'
})
export const mutations = {setName
setTemperature(state, payload) {
state.temperature = payload
}
}
export const actions = {
getWeatherData({ commit }) {
console.log("set weather to 123")
commit('setTemperature', '123')
}
}
export const getters = {
storeTemperature(state) {
return state.temperature
}
}
export const mixin = {
computed: {
...mapGetters([{
myTemperature: 'weather/storeTemperature'
}])
},
methods: {
...mapActions({
loadWeatherData: 'weather/getWeatherData'
})
}
}
export default mixin
Now i have a simple component to display the temperature:
<template>
<div class="label">
{{ testB }}
</div>
</template>
<style lang="scss" src="./Label.scss"></style>
<script>
import { mixin as WeatherMixin } from '../../store'
export default {
name: 'Label',
//mixins: [WeatherMixin],
props: {
content: {
Type: String,
default: ''
}
},
computed: {
testB () {
return this.$store.state.store.temperature
}
}
}
</script>
I tried to use mapGetters and use it like:
testB () {
return this.myTemperature
}
but this didn't work.
So i tried to use mapState via:
// store
computed: {
...mapState({ temperature: 'temperature' })
}
and use it in the component like
<div class="label">
{{ temperature }}
</div>
But i always didn't get the default value of 1234.
The Vue Console didn't find the store:
But forward to the NuxtJS documentation:
Nuxt.js will look for the store directory, if it exists, it will:
Import Vuex,
Add the store option to the root Vue instance.
What i need to get the store working as expected and how i can access the store state properties? Did i miss something?
To properly manage Vuex in NuxtJS, use namespaced Vuex modules, to keep your state logic properly organized.
For a globally accessible state, you can add these to the ./store/index.js file, while for namespaced modules create a folder with a name of your namespaced module (Preferably no spaces.), then in this folder create files with names actions.js for actions, state.js for states, getters.js for getters and mutations.js for your mutations and export default from each.
You can then access the namespaced state with this.$store.state.<NAMESPACED MODULE NAME>.<STATE VARIABLE>

Changing a vuex state from a different component?

I have a component (modal) which relies on a store. The store has the state of the modal component - whether it is active or not.
I need to be able to call this modal to open from other components or even just on a standard link. It opens by adding an .active class.
How can I change the state of the store - either by calling the stores action or calling the modal components method (which is mapped to the store).
Modal Store:
class ModalModule {
constructor() {
return {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
};
}
}
export default ModalModule;
Vue Component:
<template>
<div class="modal" v-bind:class="{ active: active }">
<div class="modal-inner">
<h1>modal content here</h1>
</div>
<div class="modal-close" v-on:click="this.toggleActive">
X
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapGetters('Modal', [
'active',
])
},
methods: {
...mapActions('Modal', [
'toggleActive',
]),
}
}
</script>
And somewhere else I want to be able to have something like:
<button v-on:click="how to change the state??">OPEN MODAL</button>
Edit:
Here's the store:
import Vuex from 'vuex';
import ModalModule from './ModalModule';
class Store extends Vuex.Store {
constructor() {
Vue.use(Vuex);
super({
modules: {
Modal: new ModalModule(),
}
});
};
}
You do not need an action for your particular usecase . You just just define a mutation as you are just changing the boolean value of a property in a state. Actions are for async functionality. You usecase is just synchronous change of Boolean value
So you can do
<button v-on:click="$store.commit('toggleActive')">OPEN MODAL</button>
EDIT:
Just export a plain object
const ModalModule = {
namespaced: true,
state: {
active: false,
},
mutations: {
toggleActive: (state) => {
return state.active = ! state.active;
},
},
actions: {
toggleActive({ commit }) {
commit('toggleActive');
},
},
getters: {
active: state => {
return state.active;
}
}
}
export default ModalModule;// export the module
Even remove the class based definition of the store
import Vue from 'vue'
import Vuex from 'vuex';
import ModalModule from './ModalModule';
Vue.use(Vuex);
export const store = new Vuex.Store({
modules: {
ModalModule
}
});
And change it like this in you component for mapping of the mutation (<MODULE_NAME>/<MUTATION_NAME>)
...mapMutations([
'ModalModule/toggleActive'
])
You can access the store from your components via this.$store. There you can call your actions and mutations. So
<button v-on:click="$store.commit('your mutation name', true)">OPEN MODAL</button>