Calling child component directly causes null values from vuex - vue.js

I am using VueJS with webpack, vuex and vue-router. I try to check if the user is registered or not by using the computed functionality in a child component. The computed functionality trys to look at the user-property in my vuex. This works fine, if i open my webapplication by this URL http://localhost:8080. The Problem occurs only if i call my child component directly by this URL: http://localhost:8080/meetup/id. It appears an error of: TypeError: Cannot read property 'registeredMeetups' of null, because user-property doesnt even exist at this time.
Child component computed Code:
computed: {
userIsRegistered() {
console.log(this.$store.getters.getUser)
if (this.$store.getters.getUser.registeredMeetups.findIndex(meetupId => {return meetupId === this.meetupId;}) >= 0) {
return true;
} else { return false; }
}
}
The user properties are set in main.js in Vue-instance:
new Vue({
router,
store,
vuetify,
render: function (h) { return h(App) },
created () {
this.initialFirebaseConfig();
this.isUserAuthSignIn();
this.$store.dispatch('loadMeetups');
},
methods: {
initialFirebaseConfig() {
const firebaseConfig = {
...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
},
isUserAuthSignIn() {
firebase.auth().onAuthStateChanged((user) => {
if(user) {
this.$store.dispatch('autoSignIn', user)
}
});
}
}
The vue-router Code:
{
path: "/meetup/:id",
name: "Meetup",
props: true,
component: () => import(/* webpackChunkName: "meetup" */ '../components/Meetup/Meetup.vue')
},
Goal: The computed functionality from the child component should run after the functionality of the vue-instance from main.js.

const user = this.$store.getters.getUser
if (user && user.registeredMeetups.findIndex(meetupId => {return meetupId === this.meetupId;}) >= 0) {
return true;
} else {
return false;
}

Related

Getting access to varaibles when testing Vue with Jest

I am using the structure below in my Vue.js web application. I am now trying to implement testing to it. But when trying to test the exampleOfFunction it says that this.exampleOfData2 is undefined.
<template>
*Some HTML*
</template>
<script>
*Some Imports*
export default {
data() {
return {
exampleOfData1: [],
exampleOfData2: 100
},
methods: {
exampleOfFunction:function(){
if(this.exampleOfData2 === 100)
{
return false;
}
return true;
},
created() {
},
mounted() {
}
}
</script>
In my testfile I then try to access the code above and I succeed with console.log(FileToTest.data()); I can see the values of data and I can access the function with FileToTest.methods.exampleOfFunction(); but when I call the function it says that this.exampleOfData2 is undefined.
It looks like you're using the component options definition instead of the component instance in your tests.
You should be creating a wrapper by mounting the component, and then you could access the component method via wrapper.vm:
import { shallowMount } from '#vue/test-utils'
import FileToTest from '#/components/FileToTest.vue'
describe('FileToTest', () => {
it('exampleOfFunction returns false by default', () => {
const wrapper = shallowMount(FileToTest)
expect(wrapper.vm.exampleOfFunction()).toBe(false)
})
it('exampleOfFunction returns true when data is not 100', () => {
const wrapper = shallowMount(FileToTest)
wrapper.setData({ exampleOfData2: 0 })
expect(wrapper.vm.exampleOfFunction()).toBe(true)
})
})

Cannot access vuex mutation in dynamically registered module

I have created a module to grab api data and hold it for a vue component:
import Vue from 'vue';
export default {
namespaced: true,
state() {
return {
all: []
}
},
getters: {
all(state) {
return state.all;
}
},
mutations: {
all(state, data) {
Vue.set(state, 'all', data);
},
clear(state) {
Vue.set(state, 'all', []);
}
},
actions: {
async init({ commit, state }) {
let response = await this.axios.get('/api/v1/teams/', {
params: {
includes: 'users'
}
});
if (response.status === 200) {
let data = response.data.data.filter(function (element) {
return element.users.length > 0;
});
commit('userTeamSelect/all', data, {root: true});
} else {
Vue.toasted.error('Could not fetch team data', {
duration: 10000
});
}
}
}
}
I have a component that is dynamically registering the module when it is created and removing the module from the store before it is deleted:
created() {
if (this.$store.getters['userTeamSelect/all'] === undefined) {
this.$store.registerModule('userTeamSelect', UserTeamSelectModule);
this.$store.dispatch('userTeamSelect/init');
}
},
beforeDestroy() {
if (this.$store.getters['userTeamSelect/all'] !== undefined) {
this.$store.commit('userTeamSelect/clear');
this.$store.unregisterModule('userTeamSelect');
}
},
When I navigate to a page where this component is used, I get the following error message in the console:
[vuex] unknown mutation type: userTeamSelect/all
This mutation is only called within the init action of the dynamic module, so the problem must be there somewhere. I have tried calling it both locally commit('all', data) and globally commit('userTeamSelect/all', data, {root:true}) with no luck.
The module must have been added to the vuex store because I can call the action without issue. It just seems the mutation isn't being registered somehow? I would expect it to work fine, or recieve an error about an unknown action.
Can anyone point me in the right direction please?

How to access Vue $refs in a plugin?

methods: {
async create () {
this.disableSubmit = true;
await this.$firestore
.collection('collectionName')
.add(this.item)
.then(() => {
this.$refs.createForm.reset();
this.$notify('positive', 'Item successfully created!');
})
.catch(error => {
this.$notify('negative', 'ERROR! Try again later!', error);
});
this.disableSubmit = false;
},
}
If I use the code above inside the methods property, then everything works fine, but I would like to access that ref from outside the Vue component, for example a plugin, but it gives me an error.
TypeError: "_this.$refs is undefined"
Even when I just import it as a function, the error is the same, so I would like to know how to access the ref outside vue?
Bellow is the code for my plugin, and I would also like to point that I am using the quasar framework.
export let plugin = {
install (Vue, options) {
Vue.prototype.$plugin = async (collection, item) => {
return await firestore
.collection(collection)
.add(item)
.then(() => {
this.$refs.createFrom.reset();
notify('positive', 'Booking successfully created!');
})
.catch(error => {
notify('negative', 'ERROR creating booking! Try again later!', error);
});
};
}
};
I hope my question makes sense, and thanks in advance for any help
you could pass the context of your component, to apply the reset form from your plugin:
// plugin declaration
Vue.prototype.$plugin = async (collection, item, ctx) {
...
ctx.$refs.createFrom.reset()
...
}
then when u call to your plugin from yours components can do it like this:
// your component
methods: {
myFunction () {
this.$plugin(collection, item, this)
}
}
this is the reference of the context of your current component that will be used inside of your plugin
for example:
Vue.component('my-form', {
methods: {
resetForm() {
console.log('the form has been reset')
}
}
})
Vue.prototype.$plugin = (item, ctx) => {
console.log('item passed:', item)
ctx.$refs.refToMyForm.resetForm()
}
new Vue({
el: '#app',
data: {
item: 'foo'
},
methods: {
submit() {
this.$plugin(this.item, this)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<my-form ref="refToMyForm"></my-form>
<button #click="submit">submit</button>
</div>

Vue.js: change parent variable from multiple layer down

This is my Vue Instance:
const app = new Vue({
el: '#app',
data() {
return {
loading: false
}
},
components: {
App,
Loading
},
router,store,
})
How can I change the loading variable from multiple layer down? It's not just simple parent/child change. It can be greatgrandparent/child change too.
There are several options, but the best way to answer the questions is to register as a global plug-in.
See the following solutions:
Create loading component and register it as a Vue component.
// loadingDialog.vue
<template>
<!-- ... -->
</template>
<script>
import { Loading } from 'path' // Insert the `loading` declared in number 2.
export default {
beforeMount () {
Loading.event.$on('show', () => {
this.show()
})
},
methods: {
show () {}
}
}
</script>
Creating a global plug-in
const loading = {
install (Vue, opt = {}) {
let constructor = Vue.extend(LOADING_COMPONENT)
let instance = void 0
if (this.installed) {
return
}
this.installed = true
Vue.prototype.$loadingDialog = {
show () {
if (instance) {
instance.show() // function of loading component
return
}
instance = new constructor({
el: document.createElement('div')
})
document.body.appendChild(instance.$el)
instance.show() // function of loading component
}
}
}
}
All components are accessible through the prototype.
this.$loadingDialog.show() // or hide()
Note, it is recommended that you control the api communication using the axios in the request or response interceptorof the axios at once.

VueJS - Accessing store data inside mounted

I'm having trouble understanding the following:
I have a store which contains variables needed for the application. In particular, there is a globalCompanies which stores:
globalCompanies: {
current: [],
all: [],
currentName: "",
}
Inside another component, I want to do the following:
mounted() {
this.$store.dispatch( "fetchUsers" );
var currentName = this.$store.state.globalCompanies.currentName;
console.log(currentName);
},
However, this just shows as empty. I know the value is there because I have computed which returns the currentName and it works fine inside the view itself. It just doesn't like the fact that it's in the mounted component.
Where am I going wrong and what can I do to resolve this issue? I really need to capture the companies Name in order to use it for some real time events.
As a result of our discussion:
In the question Vuex state value, accessed in component's mounted hook, returns empty value, because it is set in an async action which does not resolve before mounted executes.
When you need to trigger some function when async action in Vuex resolves with a value, you can achieve it using watch on a computed property, which returns a value from your Vuex state. When a value in store changes, the computed property reflects these changes and watch listener executes:
const store = new Vuex.Store({
state: {
globalCompanies: {
test: null
}
},
mutations: {
setMe: (state, payload) => {
state.globalCompanies.test = payload
}
},
actions: {
pretendFetch: ({commit}) => {
setTimeout(() => {
commit('setMe', 'My text is here!')
}, 300)
}
}
})
new Vue({
el: '#app',
store,
computed: {
cp: function() { // computed property will be updated when async call resolves
return this.$store.state.globalCompanies.test;
}
},
watch: { // watch changes here
cp: function(newValue, oldValue) {
// apply your logic here, e.g. invoke your listener function
console.log('was: ', oldValue, ' now: ', newValue)
}
},
mounted() {
this.$store.dispatch('pretendFetch');
// console.log(this.cp, this.$store.state.globalCompanies.test); // null
// var cn = this.$store.state.globalCompanies.test; // null
// console.log(cn) // null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
{{ cp }}
</div>
VueJS - Accessing Store Data Inside Mounted
Ran into this issue and it turned out to be a scope issue.
Store:
export default () => {
items:[],
globalCompanies:{
current:[],
all:[],
currentName: "Something"
},
ok: "Here you go"
}
Getters:
export default {
getGlobalCompanies(state){
return state.globalCompanies;
}
}
Mounted: This works...
mounted() {
// Initialize inside mounted to ensure store is within scope
const { getters } = this.$store;
const thisWorks = () => {
const globalCompanies = getters.getGlobalCompanies;
}
},
This is Bad: Reaching for the store outside the mounted scope
mounted() {
function ThisDontWork() {
const { getters } = this.$store; // this.$store == undefined
}
ThisDontWork();
},