I'm trying to use vue-router to show different components for different route. However it doesn't seem to be working.
I have a codepen of the compiled program here.
My main.js is just defining the router and starting the vue application. It's importing the components and setting the routes.
import scss from './stylesheets/app.styl';
import Vue from 'vue';
import VueRouter from 'vue-router';
import Resource from 'vue-resource';
import App from './components/app.vue';
import Home from './components/home.vue';
import Key from './components/key.vue';
import Show from './components/show.vue';
// Install plugins
Vue.use(VueRouter);
Vue.use(Resource);
// Set up a new router
var router = new VueRouter({
mode: 'history',
routes:[
{ path: '/home', name: 'Home', component: Home },
{ path: '/key', name: 'Key', component: Key },
{ path: '/show', name: 'Show', component: Show },
// catch all redirect
{ path: '*', redirect: '/home' }
]
});
// For every new route scroll to the top of the page
router.beforeEach(function () {
window.scrollTo(0, 0);
});
var app = new Vue({
router,
render: (h) => h(App)
}).$mount('#app');
My app.vue is really very simple, just a wrapper div and the router-view
<script>
export default {
name: "app"
}
</script>
<template lang="pug">
.app-container
router-view
</template>
The three other components that router should be showing are equally as simple, each looks basically the same. Just the name and the h1 content are different.
<script>
export default {
name: "home"
}
</script>
<template lang="pug">
h1 Home
</template>
Webpack build everything into an app.js without any errors. I have a super simple index.html file that I open in Chrome.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sagely Sign</title>
</head>
<body>
<div id="app"></div>
<script src="js/app.js"></script>
</body>
</html>
Looking at the console I see no errors. What I do notice is the URL stays the same, it looks like the router is not redirecting to /home.
file:///Users/username/Development/test-app/build/index.html
I would have expected it to change to the new route.
file:///Users/username/Development/test-app/build/index.html#/!/home
But even if I goto that route directly the home.vue component is not displayed.
The function you are using in the beforeEach method is a navigation guard. Navigation guards receive 3 parameters: to, from and next. From the Navigation Guards documentation:
Make sure to always call the next function, otherwise the hook will never be resolved.
Here, you just scroll at the top of the page but the hook is never resolved, therefore the router stops here, right after the scroll.
Write your function like this:
router.beforeEach(function (to, from, next) {
window.scrollTo(0, 0);
next();
});
And it should work.
Related
I'm trying to use vue-router based on the examples, such as
let routes = [
{ path: '/', component: MainComponent },
];
let router = new VueRouter({routes});
new Vue({ router }).$mount('#app');
but I always get this error:
vue.runtime.esm.js:619 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available.
Can I fix this by using the render function? I tried,
let routes = [];
But still fails.
Ok, I spent a half day on this and I finally got it working: vue-router + webpack + runtime-only vue.
This tutorial was the most helpful. What I learned:
If you use vue-cli, the vue version is in the webpack.base.conf.js
vue.esm.js will include the compiler
vue.runtime.esm.js will NOT include the compiler
If you want to use runtime, you must change your main.js. Do NOT use this
new Vue({
el: '#app',
router,
template: '<App/>', // <== This is bad
components: { App }
});
and instead DO use this
Vue.use(VueRouter); // <== very important
new Vue({
router,
render(createElement) {
return createElement(App);
}
}).$mount('#app');
You can use either $mount or the el: with runtime. Both work, but the $mount gives you more flexibility. And of course the router is created the usual way
let routes = [
{ path: '/', component: MainComponent },
];
let router = new VueRouter({ routes });
And if you are still seeing the error
[Vue warn]: You are using the runtime-only build of Vue
in the console, then make double sure that you never ever in your code use any templates with strings. Even inside your *.vue files, if you try to do
const Foo = { template: '<div>foo</div>' };
it will fail. Instead you must either use <template> tags, or else you must use createElement. Good luck!
The accepted solution is right in that you cannot use string template with runtime only Vue, and you have to implement the render function. However, if you want to render components at the top level instead of using router-view, you can get the matched component and render it manually:
const routes = [
{ path: '/about', component: About },
{ path: '/index', component: App },
{ path: "*", component: App }
]
new Vue({
router,
render: h => {
return h(router.getMatchedComponents()[0])
},
}).$mount('#app')
It appears to be impossible to use the runtime, based on these two MREs (one with, one without runtime). If nothing else, you can use these snippets if you choose to post an issue on their github or the vue forums, or another answer can use them as a template to prove me incorrect. This assumes you're not using vue-cli. With vue-cli you need to opt-in to including the compiler in your builds. See https://cli.vuejs.org/config/#runtimecompiler and https://v2.vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
Fails (console warning - vue runtime)
let routes = [
{
path: "",
component: {
render(h) {
return h("div", "hello world");
}
}
}
];
let router = new VueRouter({ routes });
new Vue({ router }).$mount("#app");
<script src="https://unpkg.com/vue/dist/vue.runtime.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-view></router-view>
</div>
Works (no console warning - full vue)
let routes = [
{
path: "",
component: {
render(h) {
return h("div", "hello world");
}
}
}
];
let router = new VueRouter({ routes });
new Vue({ router }).$mount("#app");
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-view></router-view>
</div>
For Vue 3 (here with TypeScript), the trick is to use the h component render function as the result of the setup function as the top-level component instead of the default createApp({}) which appears to be interpreted as a template.
So I have:
const app = createApp({
setup() {
const component = computed((): Component => {
const res = router.currentRoute.value.matched
if (res.length > 0 && res[0].components)
return res[0].components.default
else
return Loading
})
return () => h(component.value)
}
})
...
vue-router's Router exposes a ready-to-render component in property currentRoute.value.matched[0].components.default. Be sure to check for a match and for the component to be loaded (this works for lazy components as well imported using () => import('path/to/Component.vue')).
Obviously, the router is integrated into the app and and the app mounted this way, with the router being created using createRouter:
...
const router = createRouter({
history: createWebHistory(),
routes: [
...
]
})
app.use(router)
app.mount('#app')
In the index note that <router-view/> is not used. Probably was the only thing using the runtime compiler, think about a waste of space as the recommended option in the docs! The little extra effort in the root component to make it static is definitely worth it. The index looks like this:
<!DOCTYPE html>
<html class="dark" data-theme="dark">
<head>
<meta charset="utf-8"/>
<title>Title</title>
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<div id="app">
</div>
</body>
</html>
Tested with Vite, allows to have vue: 'vue/dist/vue.runtime.esm-bundler.js'. Pretty compact!
I have a vue application which dose simple inserts and i am trying to use vue router to redirect to different pages for example when i load /postComponent and /userComponent they will be on separate pages not all in one page when loading the localhost:8080
app.js
<template>
<div id="app">
<router-link to="/postcomponent">post</router-link>
<router-link to="/usercomponent">user</router-link>
<router-view/>
</div>
</template>
<script>
import PostComponent from './components/postComponent';
import userComponent from './components/userComponent';
export default {
name: 'app',
components: {
PostComponent,
userComponent
}
};
</script>
routes.js
import Vue from 'vue'
import App from '/client/src/App'
import VueRouter from 'vue-router'
import postComponent from '/client/src/components/postComponent';
import userComponent from '/client/src/components/userComponent';
vue.use(VueRouter);
Vue.config.productionTip = false
const routes = [
{
path: '/postcomponent',
name: 'postcomp',
component: postComponent
},
{
path: '/usercomponent',
name: 'usercomp',
component: userComponent
},
];
new Vue({
routes,
render: h => h(App),
}).$mount('#app')
postComponent
<script>
import postService from '../postService';
export default {
name: 'postComponent',
data() {
return {
posts: [],
error: '',
topic: '',
price: '',
location: '',
provider: ''
}
},
index.js
const express = require ('express');
const bodyparser = require ('body-parser');
const cors = require ('cors');
const app = express();
app.use(bodyparser.json());
app.use(cors());
const posts = require('./api/posts');
const users = require('./api/users');
app.use('/api/posts', posts);
app.use('/api/users', users);
const port = process.env.port || 500;
app.listen(port, () => console.log(`Server started on port ${port}`));
im getting the following error
https://i.stack.imgur.com/42Ka3.png
UPDATE ** :
App.vue :
<template>
<div id="app">
<router-link to="/postcomponent">post</router-link>
<router-link to="/usercomponent">user</router-link>
<router-view/>
</div>
</template>
Inside Routes.js import statements and paths :
import PostComponent from "#/components/PostComponent";
import UserComponent from "#/components/UserComponent";
const routes = [
{
path: '/postcomponent',
name: 'postcomp',
component: PostComponent
},
{
path: '/usercomponent',
name: 'usercomp',
component: UserComponent
},
];
PostComponent :
<template>
Post Comp
</template>
<script>
export default {
name: "PostComponent"
}
</script>
User Comp :
<template>
User Comp
</template>
<script>
export default {
name: "PostComponent"
}
</script>
UPDATE * :
remove those unnecessary imports on app file
I think its better to first have a look at Vue-router at https://router.vuejs.org/
SPA stands for Single Page Application means that all of your components eventually will execute on a single *.html file. this means that in SPA, you cant just redirect your client to another url and perform another HTTP request and still be on the same Vue SPA!. because then you will probably get new html/css and javascript files to execute and render.all you can do is 1.use vue router to specify on which path, what component should render. when you describe your router using VueRouter thats exactly what you are going to do!, config your router and tells him on which path, what component you should render.
there is no way that you can redirect your client to another domain and still be on the same SPA and loads your components BUT !
as i can see in your codes there is a way to achieve what you want. there are some problems in your code but i'll show you how you can config two routes to achieve something like that.
i assume that you have those two components postComponent and userComponent ready.
first we have to import those in our routes.js :
import postComponent from 'PATH_TO_COMP';
import userComponent from 'PATH_TO_COMP';
note that you can use '#' as an alias for /src in your directory
first we have to specify two routes, for /postcomponent and /usercomponent,
we doing it by adding two objects to routes array in VueRouter, we can specify a name for our routes and we must specify a component which will render on that router,
routes : [
{path : "/postcomponent", name :"postComp", component:postComponent},
{path : "/usercomponent", name :"userComp", component:userComponent}
]
so now we have to implement our app file to say that where we want this components to render, we use empty <router-view/> tag to show that,
now everything is set, you can switch between routes using <router-link> tag like below :
<router-link to="/postcomponent" >Show me post component :)</router-link>
and you will see that when you go to http://localhost:8080/postcomponent your postComponent will render and sits on your <router-view/> !
I have a really simple minimal example page with vue-router which is only partially working.
app.js
import 'babel-polyfill';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './app.vue';
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')
app.vue
<template>
<div class="container">
<div id="nav">
<router-link to="/">Page 1</router-link>|
<router-link to="/page2">Page 2</router-link>
<router-link to="/sretbs">No page</router-link>
</div>
<div style="border:5px solid green">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {};
</script>
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Page1 from './page1.vue';
import Page2 from './page2.vue';
import ErrorPage from './error.vue';
Vue.use(Router)
export default new Router({
routes: [
{ path: '/page1', name: 'page1', component: Page1 },
{ path: '/page2', name: 'page2', component: Page2 },
{ path: '*', component: ErrorPage }
],
redirect: {
"/": "page1"
}
})
The <router-view> appear to work on first page load. It shows the component I expect for whatever route is currently in the url (e.g. localhost/test#/page2), or if I explicitly route.push before binding Vue to #app it also shows the expected component.
Navigating to other routes doesn't appear to fully work, or at least it's not rendering the new route. When I add debug output to the page components like so:
<template>
<div class="container">
<h2>this is page 2</h2>
<buttons></buttons>
</div>
</template>
<script>
export default {
beforeRouteEnter: function(to, from, next) {
console.log(`Entering page 2:`);
next();
},
beforeRouteLeave: function(to, from, next) {
console.log(`Leaving page 2`);
next();
},
beforeRouteUpdate: function() {
console.log(`Before route update`);
}
};
</script>
it is picking up the routeenter/leave events. I can seemingly navigate back and forth between routes, but you don't see the result until a page reload
I can't recreate the problem in a jsfiddle, and I've scrapped everything and started again but am getting the same result. I can't see anything I'm doing wrong from looking at the Vue Router documentation. Any idea what I'm missing here?
In this instance the answer was painfully simple. There was another Vue instance on the page elsewhere. Even though my understanding is separate Vue instances bound to different container elements should be able to exist in harmony, in this instance it wasn't. It was also what was breaking the Vue dev tools, showing a Vuex instance for example but not showing any of the loaded components and giving a Cannot read property '__VUE_DEVTOOLS_UID__' of undefined error in the Vue Dev Tools errors.
I would like to know how can I stop component reusing in Vue-router.
I'm building a simple page application and I am unable to update data after clicking the same link twice.
Is it possible to somehow force reloading or what are the best practices in my situation?
Use the key attribute on router-view set to current url. It's built in, so no need to write any code.
<router-view :key="$route.fullPath"></router-view>
Vue Router reuses the same component therefore the mounted hook won't be called. As stated in the documentation:
The same component instance will be reused [...] the lifecycle hooks of the component will not be called.
If you want to update the data you have two options:
Watch the $route object
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
Use the beforeRouteUpdate navigation guard
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
For a more detailed explanation you can check the section Reacting to Param Changes of the Vue Router documentation: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes.
One way to do this is to put a key on the router-view and append a timestamp querystring to your router-link
const Home = {
template: '<div>Home</div>',
created() {
console.log('This should log everytime you click home.');
},
};
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: Home },
]
});
new Vue({
router,
el: '#app',
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link :to="`/?q=${Date.now()}`">/home</router-link>
<router-view :key="$route.fullPath"></router-view>
</div>
One reason not to do it this way is because it'll force rerenders on components that you may want to be reused such as on routes like
/posts/1
/posts/2
I am building a web app with two layouts (for login page, and dashboard). Each of them are represented as SPA application, so each has router-view. The main problem is 'How to connect them and redirect from one to another?'.
I have a App.vue - check if user is authorized. if yes - redirect to Dashboard.vue, else - redirect to Login.vue. Each of them has there own router-view.
An SPA should be a single html file which serves up your app and all the routes, so the basic structure should be:
HTML
<div id="app">
</div>
<!-- bundled file -->
<script src="app.js"></script>
app.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import App from './components/App.vue' // import Base component
// Import views to register with vue-router
import Login from './components/views/Login.vue'
import Dashboard from './components/views/Dashboard.vue'
const guard = function(to, from, next) {
// Check if user is logged in (you will need to write that logic)
if (userIsLoggedIn) {
next();
} else {
router.push('/login');
}
};
const routes = [{
path: '/login',
component: Login
},{
path: '/dashboard',
component: Dashboard,
beforeEnter: (to, from, next) => {
guard(to, from, next); // Guard this route
}
}]
const router = new VueRouter({
mode: 'history', // history mode
routes
})
new Vue({
el: '#app',
router,
render: h => h(App) // mount base component
})
App.vue
<template>
<div>
<!-- Your layout -->
<!-- All views get served up here -->
<router-view></router-view>
</div>
</template>
I haven't tested that, but in this scenario every view component gets served up by App.vue which is mounted on the main vue instance. You then use the beforeEach guard to check that the user is logged in, if they are then you call next() which takes them to the route, if they are not then you redirect them to login.
vue-router has the ability to create custom guards for any route. You do not need 2 separate applications, just some safety with the routes in your router.
https://router.vuejs.org/en/advanced/navigation-guards.html
Your guard could be a function that checks for authentication.
Here's a full implementation tutorial from Auth0: https://auth0.com/blog/vuejs2-authentication-tutorial/