How to properly register a component in a Vue 3 + Composition API app? - vue.js

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.

Related

error vue.js Uncaught TypeError: Cannot read properties of undefined (reading 'use')

I have made a simple blog in vue.js to practice vue.js. I have installed a router and now it doesnt wanna show anything on the localhost.
Im using directories views where i put files to show, components are for now empty. router is an own directory with a file index.js where i connect the router.
(I have not included the style in this message)
Here is the error in the console:
app.js:378 Uncaught TypeError: Cannot read properties of undefined (reading 'use')
at eval (index.js?5aa4:6:1)
at ./src/router/index.js (app.js:96:1)
at __webpack_require__ (app.js:375:33)
at fn (app.js:609:21)
at eval (main.js:4:65)
at ./src/main.js (app.js:85:1)
at __webpack_require__ (app.js:375:33)
at app.js:1497:109
at __webpack_require__.O (app.js:421:23)
at app.js:1498:53
Here is the app.vue;
<template>
<div class="container">
<div>
<img class="img" src="#/assets/bloglogo.jpg">
</div>
<div class="nav-div">
<navbar class="nav">
<ul class="navbar">
<div class="li"><router-link to="/homePosts">Home</router-link</div>
<div class="li"><router-link to="/writePost">Write a post</router-
link></div>
</ul>
</navbar>
</div>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "App",
}
</script>
here is the main.js;
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
here is the index.js inside the router directory
import vue from 'vue'
import home from '../views/homePosts'
import writePost from '../views/writePost'
import { createRouter, createWebHistory } from 'vue-router'
vue.use(createRouter,createWebHistory)
const routes = [
{
name: 'Home',
component: home,
path: '/'
},
{
name: 'writepost',
component: writePost,
path: '/writePost'
}
];
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
one of the view files;
<template>
<div class="post-container">
<h1>Blog Posts</h1>
<div class="post-mini-container">
<div class="post">
<img class="img-post" src="#/assets/person1.jpg">
<h4>Blog headline here</h4>
<h6>Writer name</h6>
</div>
<div>
<h5 class="blog-text">Lorem
</h5>
</div>
<div class="read-me">
<h6 class="read-more">Read more..</h6>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'homePosts'
}
</script>
Why are you using vue.use(createRouter,createWebHistory) in your router file?
index.js - router directory
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
name: 'Home',
component: () => import('../views/homePosts'),
path: '/'
},
{
name: 'writepost',
component: () => import('./views/writePost'),
path: '/writePost'
}
];
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
It will be much healthier if you remove the line you use vue.use(createRouter,createWebHistory) and import the components in this way. And I believe that this way the problem will be solved.
If you are wondering why we imported the components this way, I suggest you read this source (Lazy Loading).

vue.js router help what error could mean in console router-link router-view vue.js

Hi im working on the blog-app to practice vue.js. I have now made it so my nav-bar is showing but I have a problem with the routing.
Here is the errors in the console and the directories and files.
here is my main.js;
import { createApp } from 'vue'
import App from './App.vue'
import router from './routers'
import navBar from './components/navbar/navBar.vue';
const app = createApp(App)
app.component('app-navbar', navBar)
app.mount('#app')
app.use(router)
here is my app.vue
<template>
<div class="container">
<app-navbar></app-navbar>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
}
</script>
here is my routers.js;
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
name: 'homePosts',
component: () => import ('./views/homePosts'),
path: '/homePosts'
},
{
name: 'writePost',
component: () => import('./views/writePost'),
path: '/writePost'
}
];
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
here is my writePost.vue;
<template>
<div>
<form class="form">
<label for="">Writer name: </label>
<input type="text" max="500">
<br>
<label for="img">Select image:</label>
<input type="file" id="img" name="img" accept="image/*">
<br>
<label for="">Your blogtext: </label>
<textarea name="" id="" cols="30" rows="30"></textarea>
</form>
</div>
</template>
<script>
Here is my directories;
here is navbar.vue
<template>
<div class="container">
<div>
<img class="img" src="#/assets/bloglogo.jpg">
</div>
<div class="nav-div">
<navbar class="nav">
<ul class="navbar">
<div class="li"><router-link to="/views/homePosts">Home</router-link></div>
<div class="li"><router-link to="/views/writePost">Write a post</router-link></div>
</ul>
</navbar>
</div>
</div>
</template>
Firstly, you forgot to download the vue-router package or I don't know. Because as soon as I run the project, I got the error "Vue-router module not found".
To install the vue-router package:
pnpm install vue-router#latest
or
npm install vue-router#latest
Then I examined your router.js file. You should only import and use .vue files as components.
router.js
import { createRouter, createWebHistory } from "vue-router";
const routes = [
{
name: "Home",
path: "/",
redirect: { name: "homePosts" },
},
{
name: "homePosts",
component: () => import("./views/homePosts.vue"),
path: "/home-posts",
},
{
name: "writePost",
component: () => import("./views/writePost.vue"),
path: "/write-post",
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
When I came to the / path, I directed it to the /home-posts path. However, if you want, you can create a new component and assign it to / path.
Also, I suggest you give your path names as kebab-case. Like /home-posts
And finally I edited your main.js file a bit.
main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./routers";
import navBar from "./components/navbar/NavBar.vue";
const app = createApp(App);
app.use(router);
app.component("app-navbar", navBar);
app.mount("#app");
When you follow these steps, your problem should be completely resolved.
Also, as I said in the comment, there is only 1 warning message left, unfortunately I do not know the solution for it.

the switching of the Vue Router is not working

I have a menu with 2 buttons. But the switching is not working. Moreover, the main page is invisible.
My sandbox https://codesandbox.io/s/serene-neumann-mpqs0?file=/src/App.vue
This is router.js
const routes = [
{
path: "/",
component: Main
},
{
path: "/history",
component: History
},
{
path: "/favorites",
component: Favorites
}
];
const router = new VueRouter({
routes
});
export default router;
This is Header
<template>
<header class="sticky top-0 z-40 w-full flex bg-yellow-500 md:h-16 shadow-md">
<div class="w-1/4 flex justify-end">
<router-link to="/favorites">
<button title="Favorites" class="p-2 hover:text-red has-tooltip">
Favorites
</button>
</router-link>
<button class="p-2 hover:text-red" title="History">
<router-link to="/history"> History </router-link>
</button>
</div>
</header>
</template>
App.vue
<template>
<Header/>
<router-view></router-view>
</template>
<script>
import Header from './components/Header.vue'
export default {
name: "App",
components: {
Header
},
};
</script>
and main.ts
import { createApp } from "vue";
import App from "./App.vue";
import "./assets/index.css";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");
You have kind of a mix between vue-router v3/v4.
Update everything to the latest versions and adapt the Router, so it works according to the latest docs should work:
https://codesandbox.io/s/keen-morning-rufbo?file=/src/router.js

Vue warn $listeners and $attrs is readonly

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 :)

Can't change router component

I have a .vue file with the following
<template>
<div class="container">
<div class="row">
<div class="column">
<h2>Create your MIA</h2>
<p><img src="../../static/intro.gif"/></p>
<p><a v-on:click="go()" href="javascript:void(0)" class="button">Enter</a></p>
</div>
</div>
</div>
</template>
<script>
export default {
methods: {
go: function () {
console.log(this.$router.go)
this.$router.go({ path: '/app' })
}
}
}
</script>
and in a main index.html file I have
<main class="container center">
<div id="logo"></div>
<div id="section">
<router-view></router-view>
</div>
</main>
and in my main.js
import Vue from 'vue'
import Resource from 'vue-resource'
import Router from 'vue-router'
import App from './components/app'
import Intro from './components/intro'
Vue.use(Resource)
Vue.use(Router)
const route = new Router({hashbang: false,
history: true,
linkActiveClass: 'active',
mode: 'html5'})
// Pointing routes to the components they should use
route.map({
'/': {
component: Intro,
subRoutes: {
'app': {
component: App
}
}
},
'/app': {
component: App
}
})
// Any invalid route will redirect to home
route.redirect({
'*': '/app'
})
route.start(Intro, '#section')
When the stuff is compiled I am able to get the Intro component but click on the button only changes the hash but not the component....
Based on the documentation the only thing that I'm doing different is that I am changing the route programmatically.
What am I doing wrong here?