Vue warn $listeners and $attrs is readonly - vue.js

I am getting a lot of Vue warnings saying $listeners is readonly or $attrs is readonly and related to different Bootstrap items or to .
For example:
[Vue warn]: $attrs is readonly.
found in
---> <BDropdown>
<Display>
<App>
<Root>
I am very sure it has something to do with loading the Vue instance twice somehow, but I don't really know, how to do it any other way, so that the routing still works.
In my main.js the code is as follows:
import Vue from 'vue'
import App from './App'
import router from './router'
import firebase from 'firebase';
import './components/firebaseInit';
import store from './store';
import { i18n } from './plugins/i18n.js'
import BootstrapVue from 'bootstrap-vue'
import VueCarousel from 'vue-carousel';
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue);
Vue.use(VueCarousel);
let app;
firebase.auth().onAuthStateChanged(user => {
if(!app) {
app = new Vue({
el: '#app',
router,
store,
i18n,
components: { App },
template: '<App/>'
})
}
})
My router/index.js code looks as follows:
import Vue from 'vue'
import Router from 'vue-router'
import firebaseApp from '#/components/firebaseInit'
Vue.use(Router)
let router = new Router({
routes: [
{
path: '/',
name: 'display',
component: Display
},
...
]
})
// Nav Guards
router.beforeEach((to, from, next) => {
// check for requiredAuth
if(to.matched.some(record => record.meta.requiresAuth)) {
// check if NOT logged in
...
} else {
// proceed to route
next();
}
} else {
next();
}
})
export default router;
As the sample errors come from Display.vue, here is an extract of that code:
<template>
<div>
<b-row>
<b-input-group prepend="Category">
<b-dropdown v-bind:text="currentCategory">
<b-dropdown-item #click="categroyChanged('All')">All</b-dropdown-item>
<b-dropdown-item v-for="c in categories" v-bind:key="c" #click="categoryChanged(c)">{{c}}</b-dropdown-item>
</b-dropdown>
</b-input-group>
</b-row>
<div class="row" v-for="i in Math.ceil(products.length / 3)" v-bind:key="i">
<div v-for="product in products.slice((i - 1) * 3, i * 3)" v-bind:key="product.id" class="col-md-4 col-6 my-1">
<b-card
v-bind:img-src="product.thumbUrl"
img-fluid
img-alt="image"
overlay>
<div slot="footer">
<small class="text-muted">{{product.name}}<br />{{product.price}} VND</small>
</div>
<router-link v-bind:to="{name: 'view-product', params: {product_id: product.product_id}}" class="secondary-content">
<i class="fa fa-eye"></i>
</router-link>
<router-link v-if="isEmployee" v-bind:to="{name: 'edit-product', params: {product_id: product.product_id}}" class="secondary-content">
<i class="fa fa-pencil"></i>
</router-link>
<button #click='addToCart(product)' class='button is-info'><i class="fa fa-cart-arrow-down"></i></button>
</b-card>
</div>
</div>
</div>
</template>
<script>
import firebaseApp from './firebaseInit'
import { mapActions } from 'vuex'
export default {
name: 'display',
data () {
return {
txtSearch: null,
isLoggedIn: false,
currentUser: false,
isEmployee: false,
products: []
}
},
beforeMount () {
var db = firebaseApp.firestore();
db.collection('products').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const data = {
'product_id': doc.id,
'article_number': doc.data().article_number,
'barcode': doc.data().barcode,
'category': doc.data().category,
'colour': doc.data().colour,
'description': doc.data().description,
'name': doc.data().name,
'name_ger': doc.data().name_ger,
'price': doc.data().price,
'size': doc.data().size,
'thumbUrl': doc.data().thumbUrl,
}
this.products.push(data)
})
})
}
},
methods: {
...mapActions(['addToCart']),
... many methods ...
}
}
</script>
How can I get rid of these errors?

There are two common reasons why this can happen:
Multiple Vue Locations
This can be due to contradictory locations of where you are importing Vue from, in different files, as others have said. So you might have both import Vue from 'vue' and perhaps import Vue from 'vue.runtime.esm' in your code, for example.
But this can result in multiple instances of Vue, which will cause these errors.
The solution in this case is to use import Vue from 'vue' everywhere in your code, and then alias it in your packaging system (webpack, Parcel, rollup etcetera). An example of this in webpack.config.js, or webpack.renderer.config.js if you're using Electron, would be:
module.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
}
}
// ...
}
See more examples in the Vue documents.
White Listing
This can also be because of a need for Vue to be whitelisted as not one of the externals in webpack, for example.
It is worth noting that changes in Bootstrap Vue from 2.0 to a later version, definitely by 2.15 (and possibly earlier), caused this same problem to occur.
module.exports = {
// ...
externals: [
'fast-glob',
'jquery',
'bunyan',
'yaml',
'vue', // Remove this
'bootstrap-vue', // Remove this
// ...
}

After chasing this for an hour, I realized that a component that I had imported was also accessing Vue. At the top of that file was import Vue from 'vue/dist/vue.esm'. Every other file was simply doing import Vue from 'vue', which was the source of my double-import.
Different javascript packagers have different ways of resolving duplicates. For WebPack, the Resolve Configuration might be helpful in the case of dependencies importing different instances of Vue.

This was my case (https://stackoverflow.com/a/62262296/4202997) but I'll repeat it here to save you time: I was importing vue from a CDN . I simply removed the script and the problem was solved.

In my case the duplicated instances were caused by some Vue plugins importing the Vue instance differently than how I was doing in my project. I managed to fix it by adding the following to my Webpack config:
externals: {
// Stubs out `require('vue')` so it returns `global.Vue`
vue: 'Vue',
},
Hope it can help anyone struggling with the same issue :)

Related

How to properly register a component in a Vue 3 + Composition API app?

I'm trying to build an app with two layout, one with header and everything, one with just the background for some special pages(like login pages).
I've tried the following:
Created 2 view page:
layouts/Default.vue
<template>
<header class="flex justify-around">
<Header class="w-10/12 max-w-screen-lg"></Header>
</header>
<div class="grow h-full flex justify-around">
<div class="bg-white m-5 rounded-lg p-3 w-10/12 max-w-screen-lg shadow-lg">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import Header from "../components/Header.vue";
</script>
and
layouts/Plain.vue
<template>
<div class="grow h-full flex justify-around">
<div class="bg-white m-5 rounded-lg p-3 w-10/12 max-w-screen-lg shadow-lg">
<slot />
</div>
</div>
</template>
In my router/index.ts, I provide a "meta"
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/login',
name: 'login',
component: LoginView,
meta: { layout: 'plain' },
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
},
],
});
In my App.vue: I try to use this to create a component that wraps my routerView:
<script setup lang="ts">
import { computed } from "vue";
import { RouterView, useRoute } from "vue-router";
const route = useRoute();
const layout = computed(() => {
return (route.meta.layout || 'default');
});
</script>
<template>
<div id="root"
class="min-h-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex flex-col">
<component :is="layout">
<RouterView></RouterView>
</component>
</div>
</template>
And more important, in my main.ts file, I did try to register them:
import { createPinia } from 'pinia';
import Vue, { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import './assets/main.css';
import Plain from './layouts/Plain.vue';
import Default from './layouts/Default.vue';
const app = createApp(App);
app.component('default', Default);
app.component('plain', Plain)
app.use(createPinia());
app.use(router);
app.mount('#app');
But when I try to display ANY page, I get this:
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/vue.js?v=7cc2bcdd' does not provide an export named 'default' (at main.ts:2:8)
So I guess, that app.component('vue-name', component) is not the correct approach, but I can't find how?
The error is unrelated to your component registration, but rather it points to this:
👇 // The requested module 'vue.js' does not provide an export named 'default'
import Vue, { createApp } from 'vue';
In Vue 3, the vue module has no default export, so import Vue from 'vue' would fail. The Vue import also isn't used anywhere in main.ts, so you apparently don't even need it. The solution is to remove it:
import { createApp } from 'vue'; âś…
demo
According to official docs you should import them inside the App.vue and use them as variables :
<script setup lang="ts">
import { computed } from "vue";
import { RouterView, useRoute } from "vue-router";
import Plain from '../layouts/Plain.vue';
import Default from '../layouts/Default.vue';
const route = useRoute();
const layout = computed(() => {
return route.meta.layout==='plain'? Plain : Default;
});
</script>
<template>
<div id="root"
class="min-h-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex flex-col">
<component :is="layout">
<RouterView></RouterView>
</component>
</div>
</template>
You could use normal script without setup to get globally registered components, by using vite try out vite-plugin-vue-layouts to register your layouts dynamically.

Problem with router-view duplicating the components

Recently changed from laravel routing to Vue Router. Whenever I'd insert the <router-view></router-view> in any part of the file below, it'll duplicate the component which the router routes to (i.e. it will duplicate the div part of the component and insert it in the sidebar.vue file) .
For demonstration of the problem: vs
SideBar.vue file is
<template>
<aside class="container_sidebar">
<nav id="sidebar">
<ul class="list-unstyled components">
<li class="active">
<router-link to="/nghome">Home</router-link>
</li>
<li>
<router-link to="/ngsongs">All Songs</router-link>
</li>
<li>
<router-link to="/ngalbums">Albums</router-link>
</li>
<li>
<router-link to="/ngartists">Artists</router-link>
</li>
</ul>
</nav>
</aside>
</template>
My Router.js file is
import NightingaleHome from "./components/NightingaleHome";
import NightingaleSongs from "./components/NightingaleSongs";
import NightingaleArtists from "./components/NightingaleArtists";
import NightingaleAlbum from "./components/NightingaleAlbum";
import MusicUpload from "./components/MusicUpload";
export const routes = [{
path: "/home",
component: NightingaleHome,
name: "home",
},
{
path: "/ngsongs",
component: NightingaleSongs,
name: "All Songs",
},
{
path: "/ngartists",
component: NightingaleArtists,
name: "All Artists",
},
{
path: "/ngalbums",
component: NightingaleAlbum,
name: "All Album",
},
{
path: "/fileupload",
component: MusicUpload,
name: "Music File Upload",
},
];
And my App.js file is
require('./bootstrap');
import { routes } from "./router";
import VueRouter from "vue-router";
import Vue from "vue";
import Vuesax from 'vuesax';
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
import 'vuesax/dist/vuesax.css' //Vuesax styles
Vue.use(Vuesax, {
// options here
})
window.Vue = require('vue').default;
Vue.component('music-player', require('./components/MusicPlayer.vue').default);
Vue.component('music-upload', require('./components/MusicUpload.vue').default);
Vue.component('upload-progress', require('./components/UploadProgress.vue').default);
Vue.component('nightingale-songs', require('./components/NightingaleSongs.vue').default);
Vue.component('nightingale-album', require('./components/NightingaleAlbum.vue').default);
Vue.component('nightingale-artist', require('./components/NightingaleArtists.vue').default);
Vue.component('side-bar', require('./components/SideBar.vue').default);
Vue.component('edit-songs', require('./components/EditSongs.vue').default);
Vue.component('nightingale-home', require('./components/NightingaleHome.vue').default);
Vue.use(VueRouter);
let router = new VueRouter({
mode: 'history',
routes
});
const app = new Vue({
el: '#app',
router,
});
I don't have App.vue file because currently I don't need it. Any solutions?
I don't know if there help you but for similar issue, I delete the root path in my router file ( path : "/" component: App).
I think the nativ root is already charged so you don't have to built-it.

Vue.js Error in render: "TypeError: router is undefined"

I've been searching up and down all over the place for an answer to this. I'm trying to set up routes in Vue.js. I'm very new at Vue.js, so please bear with with me here.
The error is this:
[Vue warn]: Error in render: "TypeError: router is undefined"
found in
---> <RouterLink>
<SideNav> at src/components/SideNav.vue
I'm going to simplify the because the nav is fairly complex and renders correctly as long as I use standard anchors. I simplified the issue down to this:
<template>
<router-link to="/">home</router-link>
</template>
<script>
import axios from 'axios'
import Vue from 'vue'
export default ({
data () {
return {
nav: null,
}
},
mounted () {
axios
.get('http://localhost:3000/sidenav')
.then(result => (this.nav = result))
}
})</script>
If I use standard Home, I do not get the error.
If put <router-link to="/">home</router-link> in the index.html file, I do not get the above error. My main.js file looks like this:
import Vue from 'vue'
import SideNav from './components/SideNav.vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Vue.config.productionTip = false
new Vue({
render: h => h(SideNav),
}).$mount('#sidenav')
....
{ router stuff that's not important for now }
The index.html is standard, this is the block I'm attempting to inject into:
<div class="grid-x">
<div id="sidenav" class="cell small-6"></div>
<div id="infodiv" class="cell small-6"></div>
<div id="notationdiv" class="cell small-6"></div>
</div>
I found some information that this could be a config issue, so my vue.config.js file looks like this:
module.exports = {
runtimeCompiler: true,
devServer: {
proxy: 'https://localhost:3000/',
},
// resolve: {
// alias: {
// 'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' for webpack 1
// }
// }
}
It's the default config except I added runtimeCompiler: true The commented area causes an error, so it's not running.
Seems that there are 100s of examples where my pattern should be working fine, but apparently I'm missing something here. I did try adding VueRouter to SideNav.vue to no avail.

How do you access exported vm (main vue instance) object from a component?

If I start my vue instance from a main.js file
//main.js
var vm = new Vue({
el: '#app',
render: h => h(App),
router,
data: {
}
});
export {
vm
}
app.vue itself is a router view.
<template>
<router-view></router-view>
</template>
<script>
export default {}
</script>
So lets say one of the components that gets loaded in the router needs access
to vm? I've gotten as far as to do this in the component:
import vm from '../main.js'
It seems to find the main.js file. But how do I then access vm? An example of a problem is when I use vue-lazyload(https://github.com/hilongjw/vue-lazyload) and need to access vm like I try here:
<template>
<div class="hero-unit-bg" v-lazy:background-image="imgUrl" >
</div>
</template>
<script>
import Vue from 'vue'
import VueLazyload from 'vue-lazyload'
import vm from '../main.js'
Vue.use(VueLazyload)
vm.$Lazyload.$on('loaded', function ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error }, formCache) {
console.log(el, src)
})
export default {
name: 'HeroUnit',
data () {
return {
imgUrl: 'img/hero-unit-bg.png' // String
}
},
methods: {
},
}
}
</script>
Console shows vm.$Lazyload as undefined. So I don't think I'm importing vm properly. Am I missing something? Thank you.
If you are exporting like this:
export { vm }
then you need to import it like this:
import { vm } from './module.js'
For a default export, it would work like this:
export default vm
import vm from './module.js'
You'd likely create a circular dependency by importing main.js into a component. You actually don't need to reference the root instance, as the code Vue.use(VueLazyLoad) makes the plugin accessible from any component method via this.$LazyLoad.
For example, you could setup your code as follows:
main.js:
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
App.vue
export default {
...
mounted() {
this.$Lazyload.$on('loaded', function ({ bindType, el, naturalHeight, naturalWidth, $parent, src, loading, error }, formCache) {
console.log(el, src)
})
}
}
MyComponent.vue
<template>
<div v-lazy-container="{ selector: 'img' }">
<img data-src="//placekitten.com/200/200">
<img data-src="//placekitten.com/200/201">
<img data-src="//placekitten.com/200/202">
</div>
</template>
demo

Why is the activated lifecycle hook not called on first visit

I have a problem where a component within a router-view that is being kept alive does not call its activated lifecycle hook when first created. The created and mounted lifecycle hooks are being called. On a second visit, the activated hook is being called.
The scenario is quite complicated as there is a bit of nesting and slot using involved.
I've tried to create a minimal example which you can find below, or a bit more detailed on https://codesandbox.io/s/251k1pq9n.
Unfortunately, it is quite large and still not as complicated as the real code which I unfortunately cannot share.
Worse, I failed to reproduce the actual problem in my minimal example. Here, the created, mounted, and activated lifecycle hooks are all called when first visiting SlotExample.
In my real code, only the created and mounted, lifecycle hooks are called on the first visit, the activated hook is called on subsequent visits. Interestingly, all lifecycle hooks are called as expected for SlotParent.
The real code involves more nesting and makes use of slots to use layout components.
My code is using Vue 2.5.16 and Vue-Router 3.0.1 but it also doesn't work as expected in Due 2.6.7 and Vue-Router 3.0.2. I am also using Vuetify and Vue-Head but don't think think this has anything to do with my problem.
index.js.
Does anyone have an idea what I could have been doing wrong. I actually suspect a bug in vue-router
when using multiple nested slots and keep-alive but cannot reproduce.
index.js
import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import Start from "./Start.vue";
import SlotExample from "./SlotExample.vue";
const routes = [
{
path: "/start",
component: Start
},
{
path: "/slotExample/:id",
component: SlotExample,
props: true
}
];
const router = new VueRouter({ routes });
Vue.use(VueRouter);
new Vue({
render: h => h(App),
router,
components: { App }
}).$mount("#app");
App.vue
<template>
<div id="app">
<div>
<keep-alive><router-view/></keep-alive>
</div>
</div>
</template>
SlotExample.vue
<template>
<div>
<h1>Slot Example</h1>
<router-link to="/start"><a>start</a></router-link>
<router-link to="/slotExample/123">
<a>slotExample 123</a>
</router-link>
<slot-parent :id="id">
<slot-child
slot-scope="user"
:firstName="user.firstName"
:lastName="user.lastName"/>
</slot-parent>
</div>
</template>
<script>
import SlotParent from "./SlotParent.vue";
import SlotChild from "./SlotChild.vue";
export default {
name: "slotExample",
components: { SlotParent, SlotChild },
props: {
id: {
type: String,
required: true
}
}
};
</script>
SlotParent.vue
<template>
<div>
<div slot="header"><h1>SlotParent</h1></div>
<div slot="content-area">
<slot :firstName="firstName" :lastName="lastName" />
</div>
</div>
</template>
<script>
export default {
name: "slotParent",
props: {
id: {
type: String,
required: true
}
},
computed: {
firstName() {
if (this.id === "123") {
return "John";
} else {
return "Jane";
}
},
lastName() {
return "Doe";
}
}
};
</script>
SlotChild.vue
<template>
<div>
<h2>SlotChild</h2>
<p>{{ firstName }} {{ lastName }}</p>
</div>
</template>
<script>
export default {
name: "slotChild",
props: {
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
},
created() {
console.log("slotChild created");
},
mounted() {
console.log("slotChild mounted");
},
activated() {
console.log("slotChild activated");
}
};
</script>
I think you need to put SlotChild within keep-alive block.
Take a look at vue js doc about activated hook