When I want to practice a navigation bar with Vue, encountering a mistake about the invalid route component. In this div cannot display anything. As to the hint of the console, I can find no way out.
the App.vue, hereI only show the Home navigation
<template>
<div id="app">
<tab-bar>
<tab-bar-item class="tab-bar-item" path='/home' activeColor="red">
<img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/Home1.svg" alt="">
<div slot="item-text">Home</div>
</tab-bar>
<router-view></router-view>
</div>
</template>
<script>
import TabBar from './components/tabbar/TabBar'
import TabBarItem from './components/tabbar/TabBarItem'
export default {
name: 'App',
components: {
TabBar,
TabBarItem
}
}
</script>
the two vue components:
TabBarItem.vue
<template>
<div class="tab-bar-item" #click="itemClick">
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="activeStyle" :class="{active: isActive}"><slot name="item-text"></slot></div>
</div>
</template>
<script>
export default {
name: 'TabBarItem',
props: {
path: String,
activeColor: {
type: String,
default: 'red'
}
},
data() {
return {
isActive: true
}
},
methods: {
itemCilck() {
this.$router.replace();
console.log('itemclick')
}
}
}
</script>
TarBar.vue
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'TabBar'
}
</script>
index.js
import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/home/Home')
const Categroy = () => import('../views/category/Category')
const Cart = () => import('../views/cart/Cart')
const Profile = () => import('../views/profile/Profile')
// import Home from '../views/Home.vue'
const routes = [
{
path: '/home',
component: Home
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
the error
vue-router.esm-bundler.js:3266 Error: Invalid route component
at extractComponentsGuards (vue-router.esm-bundler.js:1994)
at eval (vue-router.esm-bundler.js:3107)
the console picture
According to docs they do not omit file types in dynamic imports, try Home.vue instead of just Home:
const Home = () => import('../views/home/Home.vue')
Related
I have three main sections inside my page, and I want to switch the view for one section only:
<template>
<div id="main">
<div id="scene">
<scene/>
</div>
<div id="plan">
<Plan/>
</div>
<div id="titleControl">
<router-link to="/controls"> Controls </router-link>
<router-link to="/logs"> Logs </router-link>
</div>
<div id="controlPannel">
<div id="controls">
<Controls/>
</div>
<router-view/>
</div>
</div>
</template>
router
import { createWebHistory, createRouter } from "vue-router";
import MainInterface from '../views/MainInterface.vue'
import Logs from '../views/Logs.vue'
import Scene from '../views/Scene.vue'
import Plan from '../views/Plan.vue'
import Controls from '../views/Controls.vue'
import PageNotFound from '../views/PageNotFound.vue'
const routes = [
{
path: '/',
name: 'main',
component: MainInterface
},
{
path: '/scene',
name: 'scene',
component: Scene
},
{
path: '/plan',
name: 'plan',
component: Plan
},
{
path: '/logs',
name: 'logs',
component: Logs
},
{
path: '/controls',
name: 'controls',
component: Controls
},
{
path: '/:catchAll(.*)*',
name: "PageNotFound",
component: PageNotFound,
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
I want to parse the controls as a default, and I want the user to navigate between the Logs and the Controls only, but when I click on any of the routing links it takes me to another page completely!
Can you please tell me how can I solve that? thanks in advance.
Instead of having all routes at one level, you can use Nested Routes
Change your App.vue to
<template>
<div id="main">
<div id="scene">
<ScenePage />
</div>
<div id="plan">
<PlanPage />
</div>
<div id="titleControl">
<router-link to="/controls"> Controls </router-link>
<router-link to="/logs"> Logs </router-link>
</div>
<router-view />
</div>
</template>
<script>
import PlanPage from "./Views/Plan.vue";
import ScenePage from "./Views/Scene.vue";
export default {
name: "App",
components: {
PlanPage,
ScenePage,
},
};
</script>
Add another file in view to handle nested routing such as Sub.vue with the following content
<template>
<router-view />
</template>
<script>
export default {
name: "SubPageForRouting",
};
</script>
and finally, update your router.js file as
import { createWebHistory, createRouter } from "vue-router";
import SubPageForRouting from "../Views/Sub.vue";
import LogPage from "../Views/Log.vue";
import ControlPage from "../Views/Controls.vue";
const routes = [
{
path: "/",
component: SubPageForRouting,
children: [
{
path: "",
alias: "controls",
component: ControlPage
},
{
path: "logs",
component: LogPage
}
]
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
You can find a working code sandbox Here
Is there a way to mount multiple components on a single vue instance.
I have my admin dashboard and a forum page and i don't want header and navigation to show up on these pages.
Here's what I've tried:
import App from "./App.vue";
import Admin from "./Admin.vue";
import Forum from "./Forum.vue";
const app = new Vue({
router,
store,
components: {
App, Admin, Forum
}
}).$mount("#app");
Then in my App.vue, I have other child components
<template>
<div>
<div class="general-page">
<AppHeader></AppHeader>
<transition name="fade">
<router-view></router-view>
</transition>
<AppFooter></AppFooter>
</div>
</div>
</template>
<script>
import AppHeader from "./components/AppHeader";
import Login from "./components/Login.vue";
import Register from "./components/Register.vue";
import AppFooter from "./components/AppFooter.vue";
export default {
components: {
AppHeader,
Login,
Register,
AppFooter
}
};
</script>
In Forum.vue
<template>
<div>
<div class="forum-page">
<ForumHeader></ForumHeader>
<transition name="fade">
<router-view></router-view>
</transition>
<ForumFooter></ForumFooter>
</div>
</div>
</template>
<script>
import ForumHeader from "./components/ForumHeader";
import ForumFooter from "./components/ForumFooter.vue";
export default {
components: {
ForumHeader,
ForumFooter
}
};
</script>
Admin.vue
<template>
<div>
<div class="admin-page">
<AdminHeader></AdminHeader>
<transition name="fade">
<router-view></router-view>
</transition>
<AdminFooter></AdminFooter>
</div>
</div>
</template>
<script>
import AdminHeader from "./components/AdminHeader";
import AdminFooter from "./components/AdminFooter.vue";
export default {
components: {
AdminHeader,
AdminFooter
}
};
</script>
Routes for Forum and Admin
{
path: '/admin',
name: 'Admin',
component: Admin,
meta: {
requiresAuth: true
},
children: [
{
path: '',
name: 'Profile',
component: Profile
},
{
path: 'uploads',
name: 'Uploads',
component: Uploads,
meta: {
requiresCreatorAccess: true
}
},
{
path: 'add-post',
name: 'AddPost',
component: AddPost,
meta: {
requiresCreatorAccess: true
}
}
]
},
{
path: '/forum',
name: 'Forum',
component: Forum,
children: [
{
path: '',
name: 'Channel',
component: Channel
},
{
path: 'threads',
name: 'Threads',
component: Threads
},
{
path: 'topic',
name: 'Topic',
component: Topic
}
]
},
How do I dynamically go to each route and mount each component on el: #app ?
Without changing any routing and template structure, you could use CSS to hide the app header, footer.
Another option may be to v-if the app header,footer to not render when on those routes using something like $router.currentRoute for matching.
CSS
/*
Assuming app header and footer have an id attribute
Change to your needs
*/
#app-header,
#app-footer {
display: none;
}
v-if on currentRoute
We have to do a few of things here.
Create a data variable (showMe: true)
Create a method (evaluateShowMe)
Create a watcher for the route ('$route'()) Be aware of the quotes!
Note: Feel free to rename the variable and function to suit your needs.
We need to watch $route because this is outside of a <router-view/> so we need to do this dynamically so the variable performs the evaluator function every time the route changes.
App.vue:
<template>
<div>
<div class="general-page">
<AppHeader
v-if="showMe"
></AppHeader>
<transition name="fade">
<router-view></router-view>
</transition>
<AppFooter
v-if="showMe"
></AppFooter>
</div>
</div>
</template>
<script>
import AppHeader from "./components/AppHeader";
import Login from "./components/Login.vue";
import Register from "./components/Register.vue";
import AppFooter from "./components/AppFooter.vue";
export default {
components: {
AppHeader,
Login,
Register,
AppFooter
},
data() {
return {
showMe: true
}
},
methods: {
evaluateShowMe() {
// Get the substring of the path between first and second slash
// This will allow to include any child pathing
// NOTE: In my test the first index ([0]) was empty so used one ([1]) for the `filter`
const entryPath = this.$router.currentRoute.path.split('/').filter((x,i) => i === 1);
// We want to exclude the following paths i.e. hide when on these
// There should only be one item in the array so we extract with `[0]`
return (entryPath[0] !== 'admin' || entryPath[0] !== 'forum');
}
},
watch: {
'$route'() {
this.showMe = this.evaluateShowMe();
}
}
};
</script>
I'm using async components in my code on CodeSandbox (seen below). When I click on goto product-2 after goto product-1, nothing happens. I expected the <product-item> component to change based on the clicked component, but only the URL changes. I even have a beforeRouteUpdate hook function. How do I fix this?
// main.js
import Vue from "vue";
import App from "./App.vue";
import VueRouter from 'vue-router';
import ProductPage from './product-page.vue';
Vue.use(VueRouter);
Vue.config.productionTip = false;
const routes = [
{ path: '/:productId', component: ProductPage },
]
const router = new VueRouter({
routes // short for `routes: routes`
})
new Vue({
router,
render: h => h(App)
}).$mount("#app");
// App.vue
<template>
<div id="app">
<router-link to="/product-1">goto product1</router-link>
<br>
<br>
<router-link to="/product-2">goto product2</router-link>
<div>Router view:</div>
<router-view :key="$route.params.productId"></router-view>
</div>
</template>
// product-page.vue
<template>
<div>
<product-item></product-item>
</div>
</template>
<script>
export default {
name: "product-page",
components: {
ProductItem: () => ({
component: import("./product-item.vue"),
loading: { template: "<div>loading....</div>" }
})
}
};
</script>
<template>
<div>
product item: {{product}}
</div>
</template>
<script>
export default {
name: "ProductItem",
mounted: function () {
this.product = this.$route.params.productId;
},
beforeRouteUpdate: function(to, from, next) {
this.product = to.params.productId;
next();
},
data: function () {
return {
product: null
}
},
};
</script>
The problem is the route path (i.e., /:productId) does not actually change between the links for /product-1 and /product-2 (even though the parameter values do), so router-view does not re-render.
The workaround is to key the router-view based on the productId parameter:
<router-view :key="$route.params.productId" />
Im new to Vue JS and I'm making a simple page in Vue JS. Here are my codes:
main.js
import Vue from 'vue'
import App from './App.vue'
import PokeProfile from './components/PokeProfile.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
Vue.use(ElementUI)
const router = new VueRouter({
routes: [
{path: '/', component: App},
{path: '/pokemon/:id', component: PokeProfile},
],
mode: 'history'
})
//Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App),
router: router
})
App.js
<template>
<div id="app">
<div class="tag-group">
<el-tag
v-for="pokemon in pokemons"
:key="pokemon.national_id"
:type="pokemon.name"
effect="plain">
<poke-item :pokemon=pokemon></poke-item>
</el-tag>
</div>
</div>
</template>
<script>
import PokeItem from './components/PokeItem.vue'
import axios from 'axios'
export default {
name: 'app',
components: {
PokeItem
},
data() {
return {
pokemons: []
}
},
created() {
axios.get("http://localhost:3000")
.then(res => this.pokemons = res.data)
.catch(err => {console.log(err)})
}
}
</script>
<style>
div {
display: flex;
justify-content: center;
}
</style>
PokeItem.js
<template>
<div>
<router-link :to="pokemonLink">
{{pokemon.name}}
</router-link>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
pokemon: {
type: Object,
required: true
}
},
computed: {
pokemonLink() {
return `/pokemon/${this.pokemon.national_id}`
}
}
}
</script>
PokeProfile.js
<template>
<h1>Hello Pokemon</h1>
</template>
<script>
export default {
}
</script>
The problem here is I can not move to PokeProfile.js when I click on an item in the PokeItem.js file. What could be the problem? I've checked the section of the code related to routing but I didn't see any problem.
Vue-Router uses a dynamic component (<router-view>) to render the components of your routes. Usually you will find this component in the template of your app.vue. Since you have no <router-view> component Vue-Router does not know where to render your route components.
Try this:
// main.js
import Home from './components/Home.vue'
const router = new VueRouter({
routes: [
{path: '/', component: Home},
{path: '/pokemon/:id', component: PokeProfile},
],
mode: 'history'
})
// components/Home.vue
// your old App.vue
// ./App.vue
<template>
<main>
<router-view></router-view>
</main>
</template>
I have two components - 'HelloIndex' and 'HelloShow'.
The problem is that when I try to do this
this.$router.push({name: 'HelloShow', params: {id: 1}})
, then the 'HelloIndex' component is loaded instead of 'HelloShow'.
In my router:
import Vue from 'vue'
import Router from 'vue-router'
import HelloIndex from '#/components/HelloIndex'
import HelloShow from '#/components/HelloShow'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/index',
name: 'HelloIndex',
component: HelloIndex,
children: [
{
path: ':id/show',
name: 'HelloShow',
component: HelloShow
}
]
}
]
})
HelloIndex.vue:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'helloIndex',
data () {
return {
msg: 'INDEX'
}
}
}
</script>
HelloShow.vue:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'helloShow',
data () {
return {
msg: 'SHOW'
}
}
}
</script>
App.vue
<template>
<div id="app">
<button #click="show">show</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
show () {
this.$router.push({name: 'HelloShow', params: {id: 1}})
}
}
}
</script>
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
What's wrong with the names of the components?
Parent component which has children should contain <router-view></router-view> in <template> tag. Your HelloIndex.vue file can look like:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'helloIndex',
data () {
return {
msg: 'INDEX'
}
}
}
</script>
If you want to have both components at the same level, so HelloShow won't be a child of HelloIndex you might want to edit your routes.
export default new Router({
routes: [
{
path: '/index',
name: 'HelloIndex',
component: HelloIndex
},
{
path: ':id/show',
name: 'HelloShow',
component: HelloShow
}
]
})
More informations about this topic can be found in vue-router docs