Vuex state changes are not propagated to Vue component template - vue.js

I just started working on Vue and Vuex. I have created a component with its state data in Vuex. After an action, I can see my state changes applied in mutation, however, my Vue component is still not able to pick the new changes up.
Here's my store file:
const state = {
roomInfo: {
gameID: null,
userID: null,
},
seats: null,
};
const getters = {
seats: state => state.seats,
roomInfo: state => state.roomInfo,
};
const actions = {
async streamSeats({ commit }) {
let connection = new WebSocket(`ws://localhost:8080/api/game/${state.roomInfo.gameID}/seats/${state.roomInfo.userID}`)
connection.onmessage = function(event) {
commit('setSeats', event.data);
}
connection.onopen = function() {
console.log("Successfully connected to the echo websocket server...")
}
connection.onerror = function(event) {
console.log("ERRR", event)
}
},
async setRoomInfo({ commit }, roomInfo) {
commit('setRoomInfo', roomInfo);
},
};
const mutations = {
setSeats: (state, seats) => {
state.seats = seats
// I can see changes here properly
console.log(seats);
},
setRoomInfo: (state, roomInfo) => {
state.roomInfo.gameID = roomInfo.gameID;
state.roomInfo.userID = roomInfo.userID;
if (roomInfo.seatNumber === 1) {
state.seats.p1.id = roomInfo.userID;
}
},
};
export default {
state,
getters,
actions,
mutations,
};
And this is my component:
<template>
{{ seats }}
</template>
<script>
/* import API from '../api' */
import { mapGetters, mapActions } from 'vuex';
export default {
name: "Seats",
methods: {
...mapActions([
'streamSeats',
'setRoomInfo',
]),
},
computed: {
...mapGetters([
'seats',
'roomInfo',
'setSeats',
]),
},
watch: {
roomInfo: {
handler(newValue) {
if (newValue.userID && newValue.gameID) {
this.streamSeats();
}
},
deep: true,
},
},
components: {},
data: function() {
return {
alignment: 'center',
justify: 'center',
}
},
created() {
let gameID = this.$route.params.id
this.setRoomInfo({
gameID: gameID,
userID: this.$route.params.userID,
seatNumber: 1,
});
},
}
</script>
As you can see, I'd like to change the state data for seats inside state, after it connects to websocket server.
I have spent a long time trying to figure this out with no luck. I've tried to use mapstate, data, and a few other tricks without any luck. I tried all the suggested solutions in similar stackoverflow threads as well. I'd really appreciate if someone could give me some hints on how to pass this obstacle.

There are some mismatch when you define getters and call mapGetters
store
const getters = {
seatsd: state => state.seats, // there is a typo in seats, you declared seatsd
roomInfo: state => state.roomInfo,
};
component
computed: {
...mapGetters([
'seats',
'roomInfo',
'setSeats', // this is not getters, this is mutations
]),
},

Thank you for looking at it. I installed Vuejs chrome extension today. Apparently it changed the way errors were displayed in chrome dev console. I just noticed I had a few uncaught errors elsewhere, which didn't allow the code to go through these parts properly. After resolving those issues, I was able to see the data in my component.

Related

this.$store.dispatch works but ...mapActions does not

Using vue 2 I'm trying to initialize data in vuex from a component before it loads. I can use the this.$store.dispatch and the action, along with its subsequent mutation, will run as expected from the created() function. If I call the same method in the created() function on the component using ...mapActions, the action and mutation in the module in the vuex store do not run as expected. I've looked at various ways to do this with namespacing the module (as it isn't the only module in this program), but I can't seem to get the method to run. It may be a timing in the lifecycle issue, but I'm not sure why created() wouldn't be appropriate. The addStage method is what I'm trying to have run to create the data in vuex to be displayed once the component that needs the data is loaded.
In a separate component (not shown here) I can use the ...mapActions helper by firing it from a button #click and it runs the addStage method from vuex just fine. What am I missing?
Component:
<template>
<div v-for="stage in getFlow.stages" :key="stage.id">
<flowstage v-for="step in stage.steps" :key="step.id">
<flowstep></flowstep>
</flowstage>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'flowbuildersub',
created(){
this.addStage;
//this.$store.dispatch('flowbuilder/addStage');
console.log("added stage");
},
methods: {
...mapActions('flowbuilder', ['addStage']),
},
computed: {
...mapGetters('flowbuilder', ['getFlow']),
}
}
</script>
Store:
import Vue from 'vue';
import Vuex from 'vuex';
import flowbuilder from './modules/flowbuilder.js';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
error: null,
loading: false,
information: null
},
getters: {
getError(state) {
return state.error;
},
getInformation(state) {
return state.information;
},
getLoading(state) {
return state.loading;
},
},
mutations: {
setInformation(state, payload) {
state.information = payload
},
setError(state, payload) {
state.error = payload
},
setLoading(state, payload) {
state.loading = payload
}
},
modules: {
flowbuilder
}
})
flowbuilder.js module:
export const state = {
flow: {
id: Math.ceil(Math.random()*100000000)+Math.floor(Date.now() / 1000),
name: null,
description: null,
modifiedBy: null,
stages: [],
},
};
export const getters = {
getFlow(state) {
return state.flow;
},
getStages(state) {
return state.flow.stages;
},
};
export const actions = {
addStage({ commit }) {
let defaultStep = {
id: 1,
type: "Assign",
data: null,
description: null,
subStep: null,
required: null,
selected: true,
};
let defaultStage = {
id: 1,
label: null,
description: null,
selected: false,
steps: [
defaultStep
],
};
console.log("made it here");
commit('ADDSTAGE', defaultStage);
},
};
export const mutations = {
ADDSTAGE(state, defaultStage) {
state.flow.stages.push(defaultStage);
console.log("added stage");
},
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};

Vuex action payload is undefined

I have a component that looks like this(simplified):
<script>
import { mapActions } from 'vuex';
import router from '#/router';
import { bindingService } from '#/_services/binding.service';
export default {
props: {
serialNumber: { type: String, default: ' ' }
},
data: () => ({
subscriptions: ['Loading...'],
vin: null,
}),
computed: {
splittedSerialNumber() {
return this.serialNumber.split(' ')[0];
}
},
created() {
//fetch some data
bindingService.fetchSomeData('xxx').then((data) => {
this.vin = data;
});
},
methods: {
...mapActions('binding', ['setDeviceSerialNumber', 'setVehicleIdentificationNumber']),
cancel() {
router.push('/home');
},
ok() {
console.log(this.vin); //this console.log outputs valid data
this.setVehicleIdentificationNumber(this.vin);
},
},
};
</script>
Then I have my store that look like this(simplified):
const state = {
vehicleIdentificationNumber: null,
};
const actions = {
setVehicleIdentificationNumber({ commit }, { vin }) {
console.log(vin); // this console.log outputs undefined
commit('SET_VEHICLE_IDENTIFICATION_NUMBER', vin);
}
};
const mutations = {
SET_VEHICLE_IDENTIFICATION_NUMBER(state, vin) {
state.vehicleIdentificationNumber = vin;
},
};
const binding = {
namespaced: true,
state,
actions,
mutations,
};
export default binding;
I'm even more confused because I've been using pretty much the same format of actions and mutations in this project and they work.
I'm out of ideas and looking forward to any kind of input :)
In your setVehicleIdentificationNumber method on the component, you are passing in the vin as an integer argument.
In the action, the param is an object: { vin }.
Change the action param to vin, or pass in an object in the component: { vin: this.vin }
I think the problem here is that your vin property isn't reactive because you initialized it with a null value, but you're changing it to an object. Try this:
bindingService.fetchSomeData('xxx').then((data) => {
Vue.set(this, 'vin', data)
});
Of course, you'll need to import Vue from 'vue'
You should pass the data to the action like this:
actions: {
myAction( store, payload = {myCustomKey: 'my value 1'} ){
store.commit('myCustomMutation', payload.myCustomKey);
}
}
And later уоu can call the action with or without the data:
this.$store.dispatch('myAction');
this.$store.dispatch('myAction', 'my value 2');

how to get nested getters in vuex nuxt

i have store/index.js like this
new Vuex.Store({
modules: {
nav: {
namespaced: true,
modules: {
message: {
namespaced: true,
state: {
count: 0,
conversations: [],
},
getters: {
getCount: state => {
return state.count;
},
},
mutations: {
updateCount(state) {
state.count++;
},
},
actions: {},
},
requests: {
namespaced: true,
state: {
friends: [],
},
getters: {
getFriends: state => {
return state.friends;
},
},
mutations: {
pushFriends(state, data) {
state.friends.push(data);
},
},
actions: {
pushFriends(commit, data) {
commit('pushFriends', data);
},
},
},
},
},
},
});
i want to use getters in computed property i have tested like this
computed: {
...mapGetters({
count: 'nav/message/getCount',
}),
},
butt getting error
[vuex] unknown getter: nav/message/getCount
what is am missing here
i also want to make separate folder for every modules like my nav have 3 modules message, requests & notifications
i did try but nuxt blow up my codes
I think your index is wrong, the correct thing is to separate the modules independently, something like this:
in your store/index.js
export const state = () => ({
config: {
apiURL: 'https://meuapp.com'
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is an messagem from index without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.config
}
else
// return value
return {
message: 'this is an message from index test with payload.',
result: state.config, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
here is your store/navi.js
export const state = () => ({
navi: {
options: ['aaa', 'bbb', 'ccc']
}
})
export const mutations = { }
export const actions = { }
// getters
export const getters = {
test: state => payload => {
if (!payload)
return {
message: 'this is a messagem from nav store without payload test.', // you don't need pass any payload is only to show you how to do.
result: state.navi
}
else
// return value
return {
message: 'this is an messagem from navi test with payload.',
result: state.navi, // here is your index state config value
payload: payload // here is yours params that you need to manipulate inside getter
}
}
}
then in your component you can use as a computed properties:
<template>
<div>
without a paylod from index<br>
<pre v-text="indexTest()" />
with a paylod from index<br>
<pre v-text="indexTest( {name: 'name', other: 'other'})" />
without a paylod from navi<br>
<pre v-text="naviTest()" />
with a paylod from navi<br>
<pre v-text="naviTest( {name: 'name', other: 'other'})" />
access getters from methods<br>
<pre>{{ accessGetters('index') }}</pre>
<pre v-text="accessGetters('navi')" />
<br><br>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters({
indexTest: 'test',
naviTest: 'navi/test'
})
},
methods: {
accessGetters (test) {
if (test && test === 'index' ) {
console.log('test is', test) // eslint-disable-line no-console
return this.indexTest()
}
else if (test && test === 'navi') {
console.log('test is:', test) // eslint-disable-line no-console
return this.naviTest()
}
else {
return 'test is false'
}
}
}
}
</script>
Whenever possible separate your code into smaller parts, one part for each thing. This makes it easier for you to update and keep everything in order.
Hope this helps.
I came here to find a way to access the getters of a module that was nested inside another module in Vue.js and the following solution worked for me:
this.$store.getters['outerModuleName/innerModuleName/nameOfTheGetter']
Maybe this helps someone with a similar problem.

Vuex state changes work in main store, but does not work in a module that is imported into the main store

First of all, good day everyone on this beautiful day. I created a simple jsfiddle that I'll use as an example to describe my problem.
https://jsfiddle.net/Mertakus/cokedgm2/13/
The problem I experience is that although this jsfiddle works to mutate the state of the message, in the Vue app im working on, I divided my store into modules, cuz my store gets bloated otherwise.
So I got a basic.js file where I'm doing the EXACT same thing as in the jsfille, and in my store.js file imported the file and registered the file. E.G:
export default {
strict: true,
state: {
fields: mainFields.basic,
message: 'Hello Vuex'
},
getters: {
getBasic: state => state
},
mutations: {
updateMessage (state, message) {
state.message = message
}
}
}
My main store.js file:
import Vue from "vue"
import Vuex from "vuex"
import basic from "./modules/basic"
Vue.use(Vuex)
export default new Vuex.Store ({
// strict: true,
modules: {
basic
},
// If I uncomment this, it'll work
// state: {
// message: 'Hello Vuex'
// },
// mutations: {
// updateMessage (state, message) {
// state.message = message
// }
// }
})
For some reason, this doesn't work. With the Vue devtool I figured out that the updateMessage mutations does get fired, and the payload is updated, however it isn't displayed on the screen. When I copy -> paste the above logic in the main store.js file, it does render on the screen.
Since it's vuex module, you need to use this.$store.state.basic.message instead.
const basic = {
strict: true,
state: {
message: 'Hello Vuex'
},
getters: {
getBasic: state => state
},
mutations: {
updateMessage (state, message) {
state.message = message
}
}
}
const store = new Vuex.Store({
modules: {
basic
},
})
new Vue({
store,
el: '#app',
computed: {
message: {
get () {
console.log(this.$store.state)
return this.$store.state.basic.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
})
Demo here https://jsfiddle.net/ittus/0n183xty/1/

How do I set initial state in Vuex 2?

I am using Vue.js 2.0 and Vuex 2.0 for a small app. I am initializing the store in the 'created' life-cycle hook on the root Vue instance by calling an action that retrieves the initial state from an API....like so in my Root Component:
const app = new Vue({
el: "#app",
router,
store,
data: {
vacation: {},
},
components: {
'vacation-status': VacationStatus,
},
created() {
//initialize store data structure by submitting action.
this.$store.dispatch('getVacation');
},
computed: {},
methods: {}
});
This is working just fine. Here is the action on my store that I'm calling here:
getVacation({ commit }) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
}
The mutation that this is committing with 'UPDATE_VACATION' is here:
[UPDATE_VACATION] (state, payload) {
state.vacation = payload.vacation;
},
My Problem: When I load the app, all my components that are 'getting' values from the store throw errors I'm trying to access 'undefined' values on the store. In other words, state hasn't been initialized yet.
For example, I have a component that has getters in Child Components like this:
computed: {
arrival() {
return this.$store.getters.arrival
},
departure() {
return this.$store.getters.departure
},
countdown: function() {
return this.$store.getters.countdown
}
}
All these getters cause errors because 'vacation' is undefined on the state object. It seems like an asynchronous problem to me, but could be wrong. Am I initializing my store state in the wrong spot?
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {
getVacation: state => {
return state.vacation
},
guests: state => {
return state.vacation.guests
},
verifiedGuests: state => {
return state.vacation.guests.filter(guest => guest.verified)
},
emergencyContacts: state => {
return state.emergency_contacts
},
arrival: state => {
return state.vacation.check_in
},
departure: state => {
return state.vacation.check_out
},
countdown: state => {
let check_in = new Date(state.vacation.check_in);
let now = new Date();
if ((now - check_in) > 0) {
return 'This vacation started on ' + check_in;
}
let difference = check_in - now;
let day = 1000 * 60 * 60 * 24;
return Math.ceil(difference / day) + " days until your vacation";
}
},
mutations: {
[UPDATE_VACATION](state, payload) {
state.vacation = payload.vacation;
},
[ADD_GUEST](state, payload) {
state.vacation.guests.push(payload.guest);
},
[REMOVE_GUEST](state, payload) {
state.vacation.guests.filter(guest => {
debugger;
return guest.id != payload.guest.id
})
},
[UPDATE_GUEST](state, payload) {
state.vacation.guests.map(guest => {
// Refactor Object.assign to deep cloning of object
return guest.id === payload.guest.id ? Object.assign({}, guest, payload.guest) : guest;
})
},
[ADD_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.push(payload.emergency_contact)
},
[REMOVE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.filter(contact => contact.id !== payload.emergency_contact.id)
},
[UPDATE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.map(contact => {
// Refactor not needed because emergency_contact is a shallow object.
return contact.id === payload.emergency_contact.id ? Object.assign({}, contact, payload.emergency_contact) : contact;
});
}
},
actions: {
getVacation({
commit
}) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
},
addGuest({
commit
}, guest) {
commit(ADD_GUEST, guest);
},
removeGuest({
commit
}, guest) {
commit(REMOVE_GUEST, guest);
},
updateGuest({
commit
}, guest) {
commit(UPDATE_GUEST, guest);
},
addEmergency({
commit
}, guest) {
commit(ADD_EMERGENCY, contact)
},
removeEmergency({
commit
}, contact) {
commit(REMOVE_EMERGENCY, contact)
},
updateEmergency({
commit
}, contact) {
commit(UPDATE_EMERGENCY, contact)
},
updateServer(store, payload) {
return api.saveVacation(payload)
}
}
});
Just so the solution is clear to others:
I wasn't setting my initial state quite properly in the store itself. I was pulling in the data, and updating the store correctly, but the store needed to be initialized like this:
export default new Vuex.Store({
state: {
vacation: {} //I added this, and then justed updated this object on create of the root Vue Instance
},
});
I think you're doing everything right. Maybe you're just not creating the getters correctly (can't see any definition in your code). Or your setting the initial state not correctly (also not visible in your snippet).
I would use mapState to have the state properties available in components.
In the demo simply add users to the array in mapState method parameter and the users data will be available at the component. (I've just added the getter users to show how this is working. That's not needed if you're using mapState.)
Please have a look at the demo below or this fiddle.
const api =
'https://jsonplaceholder.typicode.com/users'
const UPDATE_USERS = 'UPDATE_USERS'
const SET_LOADING = 'SET_LOADING'
const store = new Vuex.Store({
state: {
users: {},
loading: false
},
mutations: {
[UPDATE_USERS](state, users) {
console.log('mutate users', users)
state.users = users;
console.log(state)
}, [SET_LOADING](state, loading) {
state.loading = loading;
}
},
getters: {
users(state) {
return state.users
}
},
actions: {
getUsers({commit}) {
commit(SET_LOADING, true);
return fetchJsonp(api)
.then((users) => users.json())
.then((usersParsed) => {
commit(UPDATE_USERS, usersParsed)
commit(SET_LOADING, false)
})
}
}
})
const mapState = Vuex.mapState;
const Users = {
template: '<div><ul><li v-for="user in users">{{user.name}}</li></ul></div>',
computed: mapState(['users'])
}
new Vue({
el: '#app',
store: store,
computed: {
...mapState(['loading']),
//...mapState(['users']),
/*users () { // same as mapState
return this.$store.state.users;
}*/
users() { // also possible with mapGetters(['users'])
return this.$store.getters.users
}
},
created() {
this.$store.dispatch('getUsers')
},
components: {
Users
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.0.5/fetch-jsonp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.1.1/vuex.min.js"></script>
<div id="app">
<div v-if="loading">loading...</div>
<users></users>
<pre v-if="!loading">{{users}}</pre>
</div>
You can create a function that returns the initial state, and use it into your Vuex instance, like this:
function initialStateFromLocalStorage() {
...
const empty = {
status: '',
token: '',
user: null
}
return empty;
}
export default new Vuex.Store({
state: initialStateFromLocalStorage,
...
As soon as you return an object for the state, you can do whatever you want inside that function, right?