Refer `this` in App.vue cause undefined error - vue.js

My App.vue file:
<template>
<v-app app color="grey">
<Navbar v-if="isLoggedIn"></Navbar>
<v-content class="mx-4 mb-4">
<router-view></router-view>
</v-content>
</v-app>
</template>
<script>
import Navbar from "./components/Navbar";
export default {
name: "App",
components: { Navbar },
data: () => {
return {
loggedIn: true
};
},
computed: {
isLoggedIn: () => this.data.loggedIn
},
mounted() {
console.log(process.env);
},
created() {}
};
</script>
Loading the app in browser I got the error message :
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in render: "TypeError: Cannot read property 'data' of undefined"
Screenshot:

Change this:
computed: {
isLoggedIn: () => this.data.loggedIn
}
to
computed: {
isLoggedIn() {
return this.loggedIn;
}
}
Don't use an es6 arrow function or this will lose access to the proper object. Here is a good answer on Stack Overflow about that.
this.data has to be used as this.$data (otherwise it's looking for a data property on the data property), but don't do that either: you actually don't need to reference data at all, and can go directly to the variable

Using a arrow function creates a anonymous function which means this is bound to window and not the vue instance. Using function ensures the value of this is the vue instance. So change the isLoggedIn computed property to:
computed: {
isLoggedIn: function() { return this.data.loggedIn }
}
// or
computed: {
isLoggedIn() { return this.data.loggedIn }
}

Related

Dynamically Import Vue Component Based On URL Params

I'm trying to import a vue component based on a path entered into url parameters. So for instance, if <host>/<path> is entered into the browser, I want to import a vue component located at <path>.vue.
In my routes.js file, I have a route that will get nested paths:
{ path: 'object/:catchAll(.*)*', component: BaseObject }
And send it to BaseObject:
<template>
<div>
<component :is="componentFile" />
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
name: 'BaseObject',
data () {
return {
componentPath: '',
address: ''
}
},
methods: {
importComponent (path) {
return () => import(`./${path}.vue`)
}
},
computed: {
componentFile () {
return this.importComponent(this.componentPath)
}
},
created () {
const params = this.$route.params.catchAll
console.log(params)
this.address = params.pop()
this.componentPath = params.join('/')
}
}
</script>
When I navigate to http://localhost:8080/#/object/action, I'm expecting the component located at ./action.vue to be loaded. But this doesn't happen - instead I get the following errors:
runtime-core.esm-bundler.js?9e79:38 [Vue warn]: Invalid VNode type: undefined (undefined)
at <Anonymous>
at <BaseObject onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< null > >
at <RouterView>
at <QPageContainer>
at <QLayout view="lHh Lpr lFf" >
at <MainLayout onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {$i18n: {…}, $t: ƒ, …} > >
at <RouterView>
at <App>
and
Uncaught (in promise) Error: Cannot find module './.vue'
at app.js:416
Does anyone know how this can be accomplished?
There are at least 2 problems with your code...
Your "catch all" route is defined as { path: 'object/:catchAll(.*)*', component: BaseObject } so if you navigate to URL http://localhost:8080/#/object/action the "object" part is matched and catchAll param will be an array containing single item "action". So the created hook will pop this single item, params array remains empty and componentPath will be empty too (this is reason for Cannot find module './.vue' error)
In Vue 3, the old async component syntax (() => import(``./${path}.vue``)) is deprecated. You should always use defineAsyncComponent helper when creating async components (and this is reason for Invalid VNode type: undefined Vue warning)
So you BaseObject should look like this:
<template>
<div>
<component :is="componentFile" />
</div>
</template>
<script>
import { defineComponent, defineAsyncComponent } from "vue";
export default defineComponent({
name: "BaseObject",
data() {
return {
componentPath: "",
address: "",
};
},
methods: {},
computed: {
componentFile() {
return defineAsyncComponent(() =>
import(`../components/${this.componentPath}.vue`)
);
},
},
created() {
const params = this.$route.params.catchAll;
console.log(this.$route.params);
// this.address = params.pop();
this.componentPath = params.join("/");
},
});
</script>
Working demo
Also note that defining "catch all" route like this is dangerous as it will match all routes as - /object/action, /object/action/dd, /object/action/dd/bb etc. and those components will not exist. So maybe it would be better to allow only one level of nesting...
I guess that you are using Webpack to bundle your application and resolve JS modules. As you can see in the docs https://webpack.js.org/api/module-methods/#import-1, import() returns a promise, it works asynchronous. You have to resolve that promise first, before using the component.
methods: {
async getComponentFile() {
const component = await this.importComponent(this.componentPath);
return component;
}
},
Don't hard easy solutions!
<component :is="componentFile" />
export default {
name: 'BaseObject',
components: {
firstComponent: () => import('...'),
secondComponent: () => import('...'),
thirdComponent: () => import('...')
}
computed: {
componentFile () {
return this.detectComponentBasedPath()
}
},
methods: {
detectComponentBasedPath () {
...
}
}
}
</script>

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.

VueJs Data Passed From Root to Child Component via Prop Results in only an observable object

I have an app which calls a web service in the created() function and populates a property of the root data object. The property is passed via a prop to a child component and using the Chrome dev tools I can see that the prop data is available on the child component.
The problem I have is that I try to set data properties in the child component using values passed via the prop I end up with undefined property data. If I use the Chrome inspection tools and add a breakpoint I can see that the prop is an observable object in the form of {__ob__: Observer} and as such, I cannot directly access any of the data. My suspicion is that the child object sets it's data properties before the web service call has completed in the root.
How can I overcome this?
I've created a JsFiddle for this:
https://jsfiddle.net/ProNotion/a8c6nqsg/
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {
customerEmail: this.customer_data.customerEmail1
}
}
}
});
new Vue({
el: "#app",
data() {
return {
customer: {}
};
},
methods: {
init() {
var self = this;
axios.get("https://0bb1313e-b089-432e-b6bc-250f6162d7f0.mock.pstmn.io/GetCustomerData")
.then(response => {
self.customer = response.data;
}).catch(response => {
console.error(response);
});
}
},
created() {
this.init();
}
});
Here is my HTML markup:
<div id="app">
<mycomponent :customer_data="customer" />
</div>
<script type="x-template" id="my-component-template">
<div>
<p>{{form_data.customerEmail1}}</p>
</div>
</script>
Check response data type and format
console.log(typeof response.data) // string
{ "customerEmail1": "me#example.com", } // Remove `,`
You must parse to JSON type
axios.get(...).then(response => {
self.customer = JSON.parse(response.data.replace(',', ''))
})
Set property to watch with `deep` option
[Deep watching](https://v2.vuejs.org/v2/api/#vm-watch) will be detect nested value changes inside Objects
```
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {}
}
},
watch: {
customer_data: {
handler (val) {
this.form_data = val;
},
deep: true
}
}
});
```
Demo: https://jsfiddle.net/ghlee/f4gewvqn

vuex do not mutate vuex store state outside mutation handlers

[vuex] do not mutate vuex store state outside mutation handlers.
When trying to edit the fields this message is shown in the terminal.
How do I use the form correctly?
<q-editor v-model="form.email" min-height="5rem" />
-
data() {
return {
form: {
email: null
}
};
},
computed: {
...mapGetters('auth', ['getSeller'])
},
methods: {
...mapActions('auth', ['setSeller'])
},
created() {
this.form.email = this.getSeller.email;
}
--
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in callback for watcher "function () { return this._data.$$state }": "Error: [vuex] do not mutate vuex store state outside mutation handlers."
UPDATE:
<template>
<div>
<q-toggle v-model="FormState.email" />
</div>
</template>
<script>
export default {
computed: {
FormState: {
get () {
return this.$store.state.form
},
set (val) {
this.$store.commit('auth/setSeller', val)
}
}
}
}
</script>
shows the same error
Error: [vuex] do not mutate vuex store state outside mutation
handlers.
The issue is that your v-model is operating directly on this.$store.state.form.email but your computed getter only deals with the this.$store.state.form object.
Your computed getter / setter should only work on a single property if used with v-model.
For example
<q-toggle v-model="formEmail" />
computed: {
formEmail: {
get () {
return this.$store.state.form.email
},
set (val) {
// assuming this mutates the form.email state somewhere
this.$store.commit('auth/setSeller', val)
}
}
}
You can try :
<template>
<q-editor
:value="getSeller"
min-height="5rem"
#input="value => setSeller(value)" />
</template>
<script>
export default {
data() {
return {
form: {
email: null
}
};
},
computed: {
...mapGetters('auth', ['getSeller'])
},
methods: {
...mapActions('auth', ['setSeller'])
}
}
</script>

Vuejs get current route name and display on the menu

I'm using the vue-menu component, and using it to toggle between different routes. After I switch to another route I want to display the current route name on the dropdown header like so:
I've tried using the life-cycle methods such as beforeCreate , created, mounted... etc but none of them are being called. How can I achieve the desired result?
You should use the "watch"
data() {
return {
routeName: null,
};
},
watch: {
'$route': 'currentRoute'
},
methods: {
currentRoute() {
this.$nextTick(() => {
this.routeName = this.$route.name
});
}
},
<script>
export default {
computed:{
routeName(){
return this.$route.name
},
}
};
</script>
<template>
<p>Current Route is {{$route.name}}</p>
</template>
-------------------OR--------------
<script>
export default {
computed:{
routePath(){
return this.$route.path
},
}
};
</script>
<template>
<p>Current Route is {{$route.path}}</p>
</template>
This answer is for vue2.
Also, created lifecycle can also be used by directly returning the route example below
created() {
return this.$route.name
},