Vuex computed property not updating v-show - javascript

I have a simple Vuex module with one object: selectedEvent.
I am able to update my selected event using:
<Event :event="selectedEvent" />
However, I am unable to update the visibility of this property using a computed getter defined in the module:
<Event :event="selectedEvent" v-show="isEventSelected" />
My computed values in App.js:
computed: mapState({
selectedEvent: state => state.events.selectedEvent,
isEventSelected: state => state.events.isEventSelected
})
I'm aware that Vue has trouble observing some Object/ Array changes, so I have used Vue.set in my mutation. I have also attempted to move v-show inside the Event component, with no success.
If I move the getter logic inside the v-show, it works fine (however it's messy), e.g.:
<Event :event="selectedEvent" v-show="selectedEvent.hasOwnProperty('id')" />
I'm fairly new to Vue - What am I missing here?
store/modules/events.js:
import { EVENT_SELECT } from "./types";
import Vue from "vue";
const state = {
selectedEvent: {}
};
const getters = {
selectedEvent: state => {
return state.selectedEvent;
},
isEventSelected: state => {
return state.selectedEvent.hasOwnProperty("id");
}
};
const actions = {
setSelectedEvent({ commit }, selectedEvent) {
commit(EVENT_SELECT, selectedEvent);
}
};
const mutations = {
[EVENT_SELECT](state, selectedEvent) {
Vue.set(state, "selectedEvent", selectedEvent);
}
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
App.vue:
<template>
<div id="app">
<b-container>
<Calendar />
<Event :event="selectedEvent" v-show="selectedEvent.hasOwnProperty('id')"/>
</b-container>
</div>
</template>
<script>
import Calendar from "./components/Calendar.vue";
import Event from "./components/Event.vue";
import { mapState } from "vuex";
export default {
name: "app",
components: {
Calendar,
Event
},
computed: mapState({
selectedEvent: state => state.events.selectedEvent,
isEventSelected: state => state.events.isEventSelected
})
};
</script>

In your store, isEventSelected is a getter, not a state property so you should use mapGetters, eg
import { mapState, mapGetters } from 'vuex'
// snip
computed: {
...mapState('events', ['selectedEvent']),
...mapGetters('events', ['isEventSelected'])
}

Related

How to mock extended component vue.js

I have the following problem, I want to test a component in vuejs which extends another component like this :
<template>
<div></div>
</template>
<script>
import MySuperComponent from '#/project/MySuperComponent'
export default {
extends: MySuperComponent,
name: myComponent,
components: {
}
</script>
my test look like this :
import {createLocalVue, shallowMount, mount} from '#vue/test-utils'
// Component to test
import myComponent from '#/project/myComponent.vue'
// import store
import Vuex from 'vuex'
// LIB
import VueI18n from "vue-i18n"
describe(`myComponent.vue`, () => {
const localVue = createLocalVue()
let i18n, store, getters, state, mutations, actions
beforeAll(() => {
localVue.use(Vuex)
getters = {
mygetters: jest.fn()
}
state = {
getNames: jest.fn(),
}
mutations = {}
actions = {}
store = new Vuex.Store({
getters, state, mutations, actions
})
localVue.use(VueI18n)
i18n = new VueI18n({
silentTranslationWarn: true
})
})
it(`Test default mounted myComponent ok`, () => {
const wrapper = mount(myComponent, {
propsData: {
component : 'mycompnent',
json : {},
animationEnabled: true
},
computed: {
getNames : jest.fn(),
},
watch: {
ageUser : jest.fn(),
},
i18n,
localVue,
store
})
expect(wrapper.exists()).toBeTruthy()
})
})
the problem is even if my component is empty I need to set all the props of the extended component and all the computed .... I tried using jest.mock(MySuperComponent) but nothing work, my goal here is just to test what's inside my component

Vuex Mutation not updating the State

Working on a Vuejs application whereby I use Vuex for state management between the components.In Vuex store, I have an action that fetches some data from an API (which works fine) then populate it to the state (via a mutation). Next, I pass the updated state to the component using getters.
The problem is there is a problem populating data to the state from the action. In the DOM I have tried fetching via computed property or using the getter but get empty string
Vuex Store
const getDefaultState = () => {
return {
clientDeposit: ''
}
}
//state
const state = getDefaultState();
//getters
const getters = {
getDeposit: (state) => state.clientDeposit
}
//actions
const actions = {
fetchClients({ commit}) {
const clientId ="23"
axios.post('/api/motor/fetchClients', {
ClientId: clientId,
})
.then((response)=> {
//console.log(response); //returns data
const deposit = response.data;
commit('setIpfDeposit', deposit);
})
}
}
//mutations
const mutations = {
setIpfDeposit: (state, value) => (state.clientDeposit = value)
}
export default {
state,
getters,
actions,
mutations
}
Component
<template>
<div>
<button onclick="fetchClients()">Fetch Clients</button>
Deposit (Via computed property) : {{ fetchDeposit }}
Deposit (from getter) : {{ getDeposit }}
</div>
</template>
<script>
import { mapGetters , mapActions } from "vuex";
import axios from "axios";
export default {
name: "",
data() {
return {
}
},
computed: {
...mapGetters([
"getDeposit"
]),
fetchDeposit(){
return this.getDeposit
},
},
methods:{
...mapActions([
"fetchClients"
])
}
};
</script>
<style scoped>
</style>
You need to fetch the data first.
Import mapActions from vuex
import {mapActions, mapGetters} from 'vuex';
Bring in the fetchClients method in your component's methods object
methods:{
... mapActions(['fetchClients']),
}
Then in your component's created life cycle method call the fetchClients method
created(){
this.fetchClients();
}

How to use a custom auth claim to query firestore (with vuexfire)

In my vuex I have one custom auth claim saved to state as claims(state.claims). I can read this from the state in my vue components just fine and I can also see it in vue devtools.
But when I try to use state.claims to query a firestore collection,
Sometimes I get null(the default value in the state), or an error that I could not replicate saying that is was a "custom object B"
It works the first time loaded but after one refresh it breaks until I switch views back and forth
My guess is that it isn't grabbing the value fast enough for claims but I am not sure
Vuex store
import Vue from "vue";
import Vuex from "vuex";
import { vuexfireMutations, firestoreAction } from 'vuexfire'
import {db} from "#/components/fbInit.js";
import firebase from 'firebase'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
tests: [],
claims: [],
user: null,
isLoggedIn: false,
},
mutations: {
...vuexfireMutations,
setUser: state => {
if (firebase.auth().currentUser) {
state.isLoggedIn = true;
state.user = firebase.auth().currentUser;
state.claims = firebase.auth().currentUser.getIdTokenResult().then((idTokenResult) => {
state.claims = idTokenResult.claims.company
}) .catch((error) => {
// eslint-disable-next-line no-console
console.log(error);
});
}
},
},
actions: {
// GET documents in a collection/subcollection and put them into state
getFire: firestoreAction(({ bindFirestoreRef, state }) => {
//return the promise returned by 'bindFirestoreRef'
return bindFirestoreRef('tests', db.collection(state.claims))
}),
setUser: context => {
context.commit('setUser'); //context.commit to perform mutation
},
}
})
Vue Component
<template>
<div class="pa-9">
<div>
<v-card light>
{{ this.claims}}
</v-card>
<v-card >
{{ this.tests }}
</v-card>
</div>
</div>
</template>
<script>
import {db, functions } from '../components/fbInit'
import { mapState } from 'vuex'
import { mapActions } from 'vuex'
export default {
name: "Claims",
data: () => ({
}),
created() {
this.setUser()
this.getFire()
},
computed: {
...mapState ([
'events','wow','user','isLoggedIn', 'claims','tests'
]),
},
methods: {
...mapActions([ 'setUser', 'getFire', ]),
});
}
},
};
</script>
ERRORS
FirebaseError: Function Firestore.collection() requires its first argument to be of type non-empty string, but it was: a custom B object
[Vue warn]: Error in render: "TypeError: Converting circular structure to JSON
--> starting at object with constructor 'B'
| property 'c' -> object with constructor 'B'
| property 'b' -> object with constructor 'Wb'
--- property 'a' closes the circle"
FirebaseError: Function Firestore.collection() requires its first argument to be of type non-empty string, but it was: null
EDIT Main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
require('dotenv').config();
import vuetify from './plugins/vuetify';
// require('#/components/fb.js')
import firebase from 'firebase/app'
import './components/fbInit'
import * as VueGoogleMaps from 'vue2-google-maps';
Vue.config.productionTip = false
/* eslint-disable */
let app;
firebase.auth().onAuthStateChanged(user => {
if(!app) {
app = new Vue({
router,
vuetify,store,
render: h => h(App)
}).$mount('#app')
}
})
I found some doc . Looking at example there state in firestoreAction(({ bindFirestoreRef, state }) should be after }. So it should be:
firestoreAction(({ bindFirestoreRef }, state )
however I am not sure if it will work without unbindFirestoreRef...
I hope it will help you!

How to map state back to component in Vue.js AWS Amplify auth page

I am building an authentication page with Vue.js, Vuex, and AWS Amplify.
This auth page is based off Erik Hanchett's AWS Auth Example (https://github.com/ErikCH/Aws-auth-example/blob/master/src/components/HelloWorld.vue). Erik's original demo utilized Vuex for state management, but for the sake of simplicity only employs the state handler in the store.js file.
I am attempting to reconfigure this demo so that the various methods and hooks in HelloWorld.vue are set up to also dispatch actions and commit mutations.
So far, I have been successful in setting up the findUser() method in HelloWorld.vue to dispatch actions, pass user and signedIn as payloads to their respective action handlers, and then commit mutations.
However, my issue now pertains to the computed property in the HelloWorld component.
Erik's original demo returns the state directly to the component using return this.$store.state.signedIn as seen in the computed property. Based on my experience with Vuex in other projects, I would normally use a mapState helper to map directly to the state.
Is it correct in this project to use this.$store.state.signedIn to return the state? Or should I use mapState? If so, how can I reconfigure this computed property in order to employ mapState to map directly to signedIn?
My code is below:
HelloWorld.vue
<template>
<div class="hello">
<div v-if="!signedIn">
<amplify-authenticator></amplify-authenticator>
</div>
<div v-if="signedIn">
<Home></Home>
</div>
</div>
</template>
<script>
import { Auth } from 'aws-amplify'
import { AmplifyEventBus } from 'aws-amplify-vue';
import { mapState } from 'vuex'
import Home from '../components/Home.vue'
export default {
name: 'HelloWorld',
components: {
Home
},
data() {
return {
login: '',
password: ''
}
},
props: {
msg: String,
},
created(){
this.findUser();
AmplifyEventBus.$on('authState', info => {
if(info === "signedIn") {
this.findUser();
} else {
this.$store.state.signedIn = false;
this.$store.state.user = null;
}
});
},
computed: {
signedIn(){
return this.$store.state.signedIn;
}
},
methods: {
async findUser() {
try {
const user = await Auth.currentAuthenticatedUser();
let signedIn = true
this.$store.dispatch('setUser', user)
this.$store.dispatch('setSignedIn', signedIn)
}
catch(err) {
let signedIn = false
this.$store.dispatch('setSignedIn', signedIn)
}
}
}
}
</script>
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: null,
signedIn: false
},
mutations: {
setUser(state, user) {
state.user = user
},
setSignedIn(state, signedIn) {
state.signedIn = signedIn
}
},
actions: {
setUser: (context, user) => {
context.commit('setUser', user)
},
setSignedIn: (context, signedIn) => {
context.commit('setSignedIn', signedIn)
}
}
})
Home.vue
<template>
<div class="goodbye">
<h1>HOME</h1><br>
<amplify-sign-out></amplify-sign-out>
</div>
</template>
<script>
import { Auth } from 'aws-amplify'
export default {
name: 'Home',
data() {
return {
login: '',
password: ''
}
},
props: {
msg: String,
},
methods: {
signOut() {
Auth.signOut()
}
}
}
</script>
The mapState helper is just sugar syntax for not repeating multiple times the whole this.$store.state.foo piece of code.
You can certainly use mapState like this
import { mapState } from 'vuex'
computed: mapState([
// map this.signedIn to this.$store.state.signedIn
'signedIn'
])
Or like this if you want to also use local properties besides the ones of mapState
import { mapState } from 'vuex'
computed:
localComputed () { /* ... */ },
...mapState([
// map this.signedIn to this.$store.state.signedIn
'signedIn'
])
Here are the docs for more information on this.

vuex and axios debugging

I'm going crazy, I have a working api that sends data, I connected it to a VueJS app and it was working fine. I'm trying to implement Vuex and I'm stuck. Here's my store.js file
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios'
Vue.use(Vuex);
const state = {
message: "I am groot",
articles: []
}
const getters = {
getArticles: (state) => {
return state.articles;
}
}
const actions = {
getArticles: ({ commit }, data) => {
axios.get('/articles').then( (articles) => {
commit('GET_ARTICLES', articles);
console.log(articles); // Trying to debug
}, (err) => {
console.log(err);
})
}
}
const mutations = {
GET_ARTICLES: (state, {list}) => {
state.articles = list;
}
}
const store = new Vuex.Store({
state,
getters,
mutations,
actions,
mutations
});
console.log(store.state.articles); // this lines works but data is empty
export default store
The console.log within axios call doesn't run and store.state.articles is empty. I must be missing something. I'm just trying to console the articles data on page load...
Please help, I'm near insanity :)
Component :
<template>
<div class="container">
<h1>Test component yo !</h1>
<p>{{ message }}</p>
</div>
</template>
<script>
export default {
name: 'Test',
computed: {
message() {
return this.$store.state.message
}
},
mounted: () => {
this.$store.dispatch('getArticles')
}
}
</script>
App.js :
import Vue from 'vue';
import ArticlesViewer from './articles_viewer.vue';
import UserArticles from './user_articles.vue';
import App from './app.vue'
import store from './store'
new Vue({
el: '#app-container',
store,
render: h => h(App)
})
You define the mounted lifecycle hook of your component using an arrow function.
As per the documentation:
Don’t use arrow functions on an instance property or callback (e.g. vm.$watch('a', newVal => this.myMethod())). As arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect and this.myMethod will be undefined.
You should define it like so:
mounted: function () {
this.$store.dispatch('getArticles');
}
Or, use the ECMAScript 5 shorthand:
mounted() {
this.$store.dispatch('getArticles');
}
Now, your dispatch method will be called correctly, populating your articles array.

Categories

Resources