Wait for mapped store getter to update child property - vuejs2

I have the following vuex getters
import { isEmpty } from 'lodash'
// if has token, we assume that user is logged in our system
export const isLogged = ({ token }) => !isEmpty(token)
// get current user data
export const currentUser = ({ user }) => user
export const timeLimit = ({ token_ttl }) => token_ttl
export const getToken = ({ token }) => token
I have the following computed Vuex properties in a child component
name: "ProfilePic",
computed: {
...mapGetters(['currentUser']),
url() {
return new String('').concat(window.location.protocol, '//', window.location.hostname , ':8000', '/games/create/?search=player_id:').valueOf()
}
},
mounted(){
console.log(this.currentUser)
},
watch: {
currentUser(value, old){
console.re.log('ok', value, old);
new QRCode(document.querySelector(".profile-userpic"), {
text: this.url + value,
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
})
}
}
the parent
import ProfilePic from '../../components/general/qrcode.vue'
export default {
name: 'CcDashboard',
methods : {
...mapActions(['checkUserToken', 'setMessage'])
},
computed: {
...mapGetters(['isLogged'])
},
mounted() {
this.checkUserToken().then(tkn => this.$store.dispatch('setMessage', {type: 'success', message: 'Your Game Starts Now!!!!'})).catch(err => this.$store.dispatch('setMessage', {type: 'error', message: ['Your time is up!']}))
},
components: {
'profile-pic': ProfilePic
}
}
Store
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules,
plugins,
getters,
strict: false, //process.env.NODE_ENV !== 'production',
})
I'm using VuexPersist with localforage
localforage.config({
name: 'vuevue'
});
const vuexLocalStorage = new VuexPersist({
key: 'vuex', // The key to store the state on in the storage provider.
storage: localforage, // or window.sessionStorage or localForage
// Function that passes the state and returns the state with only the objects you want to store.
// reducer: state => ({ Collect: state.Collect, Auth: state.Auth}),
// Function that passes a mutation and lets you decide if it should update the state in localStorage.
// filter: mutation => (true)
modules: ['Auth','Collect'],
asyncStorage: true
})
export const RESTORE_MUTATION = vuexLocalStorage.RESTORE_MUTATION
// // create a new object and preserv original keys
export default [...app.plugins, vuexLocalStorage.plugin]
executing console.log on mounted() I get
{__ob__: Observer}current_points: 45email: "qhegmann#jast.com"id: 2name: "Sandrine Cruickshank"total_points: 45__ob__: Observerdep: Dep {id: 20, subs: Array(4)}value: {id: 2, name: "Sandrine Cruickshank", email: "qhegmann#jast.com", current_points: 45, total_points: 45, …}
However,
When running the logic the this.currentUser.id returns undefined rather than a value( which it does)
Is it that I need to "wait" for it to properly populate from the store? or do I need to call it from the $store.dispatch() ?

I guess what you want here is to watch the state of your computed property itemGetter, and when itemGetter is different from null/undefined trigger the method createProductSet ? https://v2.vuejs.org/v2/guide/computed.html
computed : {
...mapGetters([
'itemGetter'
])
},
watch : {
itemGetter(newVal, oldVal) {
if (typeof newVal == null || typeof newVal == undefined)
return
this.createProductSet(newVal)
}
},
methods : {
createProductSet(id){
// logic
}
}

Figured it out and had to do with a bug with one of my packagesenter link description here

Related

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 unknown action (or mutation) type

I'm writing a simple code to set token in store in an Nuxt application. when I tried to call a mutation or action from my store, this error is logged in console: [vuex] unknown action type: setToken
import Vuex from 'vuex';
export const store = new Vuex.Store({
state:()=> ({
token: ''
}),
getters: {
getToken: state => {
return state.token;
}
},
mutations: {
setToken: (tokenStr) => {
state.token = tokenStr;
}
},
actions: {
setToken: ({ commit }, tokenStr) => {
commit('setToken', tokenStr);
}
}
})
This is a method trying to call the mutation:
methods:{
setToken(){
this.$store.dispatch('setToken','token1');
this.token = this.$store.getters.token;
}
}
You are using the 'classic' and now deprecated method of setting the vuex store in nuxt. You should set it up like this:
// store/index.js
export const state = () => ({
token: ''
})
export const mutations = {
SET_TOKEN (state, tokenStr) {
state.token = tokenStr
}
export const actions = {
setToken ({ commit }, tokenStr) {
commit('SET_TOKEN', tokenStr)
}
}
export const getters = {
token: (state) => state.token
}
Nuxt will build the store for you from there. You can see it in the doc here.
You can dispatch actions in components with this.$store.dispatch('xxx'), or use the mapActions helper which maps component methods to store.dispatch calls (requires root store injection):
Try Another Method For Dispatching An Action
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment',
// map `this.increment()` to
this.$store.dispatch('increment')
// `mapActions` also supports payloads:
'incrementBy' // map `this.incrementBy(amount)` to `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // map `this.add()` to `this.$store.dispatch('increment')`
})
}
}

Auto SignIn with Vuex and Vuex-persistedstate

I would like to auto-sign-in user when the page has been refreshed. I've read that I should use vuex-persistedstate to persist the token in localstorage. Here's my vuex store:
store: {
user: null
},
actions: {
autoSignIn ({commit}, payload) {
commit('setUser', { id: payload.token })
}
},
mutations: {
setUser (state, payload) {
state.user = payload;
}
},
plugins: [ createPersistedState({
getState: (key) => localStorage.getItem(key),
setState: (key, state) => localStorage.setItem('user_token', key)
}) ]
I also have signIn action where I create a newUser with token.
signUserIn ({commit, getters, state}, payload) {
let data = {
_username: payload.email,
_password: payload.password
}
Vue.http.post(
'url',
data,
{ channel: 'default' },
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
).then(response => {
const newUser = {
id: response.body.token
}
localStorage.setItem('user_token', response.body.token)
commit('setUser', newUser)
})
}
Then in main.js - created() I would like to check if the token is valid, afterwards - sign user in.
created() {
let token = localStorage.getItem('user_token')
if(token) {
this.$store.dispatch('autoSignIn', token)
}
}
The last part doesn't work, I know I should use getState, setState from createPersistedState but I have no idea how to do it. How do I make it work?
If the only use case for using vuex-persistedstate is to remember the access token then you should avoid using it in the first place and save yourself a few Kb from the final build file.
It would make more sense using it if you were to provide offline experience to your users.
If all you do is set state.user with the locally stored token then you could just do.
// if localStorage contains a serialized object with a 'token' attribute
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
user: userToken ? userToken.token || null : null
};
const mutations = {};
const actions = {};
export default {
state,
mutations,
actions,
}
Whenever you refresh the page and the store is being instantiated state.user will either take as default value the locally stored token or null if missing/undefined
However if i were you i would replace
const state = {
user: null
};
with
const state = {
accessToken: null
};
since all you store is the accessToken and not the user itself so its kind misleading.
update to answer the question in comments "... I need to check if the state has changed and use setUser mutation but don't how to achieve it."
There are 3 ways I can think of.
first of all change state to
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
accessToken: userToken ? userToken.token || null : null,
user: null,
};
then
The Simplest of all
on your App.vue component add a mounted method like the following
import { mapState, mapActions } from 'vuex';
export default {
...
computed: {
...mapState([
'accessToken',
'user',
])
},
mounted() {
if (this.accessToken && !this.user)
this.getAuthUser();
},
methods: {
...mapActions([
'getAuthUser',
]),
},
}
So on every refresh when the App is mounted and we have an accessToken but not a user we call getAuthUser() action which makes an ajax call and stores the received user with a setUser mutation
The Router Guard way
If you have a router and you only need to check for an authenticated user on certain routes then you can use route guards. for example
import store from '#/store';
export default new Router({
routes: [
...
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (!store.state.accessToken) return next('/login');
if (store.state.accessToken && !store.state.user) {
return store.dispatch('getAuthUser')
.then(() => {
// user was retrieved and stored and
// we can proceed
next();
})
.catch(() => {
// we couldn't fetch the user maybe because the token
// has expired.
// We clear the token
store.commit('accessToken', null);
// And go to login page
next('/login');
});
},
return next();
},
},
...
],
});
Using Vuex plugins
This is a method I've recently learned.
const storeModerator = (store, router) {
// listen to mutations
store.subscribe(({ type, payload }, state) => {
// if commit('setAccessToken') was called dispatch 'getAuthUser'
if (type === 'setAccessToken') {
store.dispatch('getAuthUser');
}
});
};
export default new Vuex.Store({
...,
plugins: [storeModerator]
});
You can learn more by checking:
Vue-router navigation guards
Vuex Plugins
Decouple Vuex modules with the Mediator pattern

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?