For some reason, in my Vue app I am having issues with the router when the linked page is only changing the slug. For example
I have the following links
<router-link :to="{ path: '/tag/tag2' }">Tag 2</router-link>
<router-link :to="{ path: '/tag/tag3' }">Tag 3</router-link>
<router-link :to="{ path: '/category/cat1' }">Category 1</router-link>
which are processed by router
{
path: '/tag/:slug',
name: 'Tag',
component: () => import('./views/tag')
}
inside views/tag.vue when it loads it does an axios request to get all the tags. Same functionality for category etc
Lets say the current URL is http://test.com/tag/tag1
If you click on the link for Tag 2 the URL will change, but nothing else happens. No axios calls etc.
If I click on Tag 3 the URL will change, but nothing else happens. No axios calls etc.
If I click on Category 1 then the url changes, page loads up the category view and fires off its axis request.
Why does it have issues if only the slug is changing?
I believe your axios calls are located in one of component's lifecycle hooks (i.e. beforeCreate). The problem is that Vue Router tries to reuse components wherever it is possible. In your case, going from /tag/tag1 to /tag/tag2, the only thing that changes is parameter so it's still the same component and the router does not recreate it again.
There are two solutions:
Put the axios calls to beforeRouteUpdate hook meaning they will be fired every time route in the component changes.
Add key attribute to router-view, which will make the router to recreate every component.
<router-view :key="$route.fullPath"></router-view>
Source: A post from a member of the core team of Vue'js
Related
My Vue app contains two main router-views: one inside of a modal, allowing content to be loaded inside said modal, and another inside the main page. I differentiate between them using the name attribute. Here is a piece of code in the routes.js file which would load content into the modal, for example.
export default ({
mode: 'history',
routes: [
{
path: '/classComp/create',
components: {
modalView: createClass
}
}
]
})
Essentially, my issue is that, when I load content into the router, the content in the router-view for the main page disappears. I understand that most people use the children attribute to address this, but that is not feasible in this case. For instance, the user can press a router-link button in the sidebar to load the form to create classes into the modal. The ability to press this button is thus unrelated to what is loaded or not loaded into the main page, so creating a children attribute is not feasible.
In short, how can I get vue-router to load content into the modal's router-view without wiping the content of the mainbox's router-view?
**EDIT: **Someone suggested to use keep-alive for this, but I could not get this to work. Is this a viable suggestion, and, if so, how would I go about it?
Thinking over your use-case again, I don't think nested routes it the way to go.
Unless you really want to have a route that points to your modal, you can use something like portal-vue to push the modal into another view.
If you are using vue 3, it comes with a component that accomplishes something similar: Teleport
EDIT - continuing our conversation from the comments
As I understand it, portal allows you to insert HTML into two separate locations at once from a single route; my issue is that I need to have 2 routes loaded simultaneously.
You're right, portal won't really allow you to change that. The problem is that vue-router does not have native support for simultaneously loading two routes (unless one is a child of the other). A couple things you could try:
Add a modal nested route (route.children). This would allow you to use <router-view name="modal"> and not navigate away from the parent view. I don't think this makes a lot of sense though. You could do it programmatically using router.addRoutes
Have two routers and two vue apps. I don't believe that vue-router has native support for loading two routes at the same time (unless one is a child of the other). You could however have a separate vue instance just for your modal code. This might introduce complexities based on the design of your app though. A quick example:
const appRouter = new VueRouter({ ... })
const modalRouter = new VueRouter({ mode: 'abstract', ... })
Vue.prototype.$modalRouter = modalRouter
const mainApp = new Vue({ router: appRouter })
mainApp.mount('#app')
....
App.vue
-------
<template>
<div id="app">
<router-view></router-view>
<div id="modal-app" v-pre>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
mounted() {
const modalApp = new Vue({ router: this.$modalRouter })
modalApp.mount('#modal-app')
}
}
</script>
You could call this.$modalRouter.push anywhere in your app to update the route for it.
Both of those solutions seem a bit hacky to me. The easiest way to do this is probably not to use vue-router at all. With portal-vue you could mount content into your modal from anywhere in the app. You can even do it programmatically with Wormhole.open().
Having an issue with my url updating but the page not.
From the home page, I display a list of projects. Clicking a project will take you to "website.com/project/project-1" and everything works as intended.
However, at the bottom of that page, I again show a list. This list is the same as homepage, with same functionality. But the problem is, is that it will update the url to "website.com/project/project-2" but the page will not re-render or change.
An example of my code
My current router-path of the component.
path: '/project/:project_slug',
name: 'ProjectPage',
component: ProjectPage
My Router Link from the project page to the new project page
<router-link :to="{ name: 'ProjectPage', params: {project_slug: projectHighlightSlug} }">
<h4 class="header-17 semibold">{{projectTitle}}</h4>
</router-link>
Update
This is my current method/watch section
methods: {
goToProject() {
this.$router.push({
name: 'ProjectPage',
params: {project_slug: this.projectHighlightSlug}
})
},
},
watch:{
// eslint-disable-next-line no-unused-vars
'$route'(to, from) {
this.goToProject();
}
}
However, the to,from is "defined but never used" and clicking my button to call goToProject() gives me the error;
"You may have an infinite update loop in watcher with expression "$route""
As explained in the Vue Router docs, when the url the user navigates to uses the same component, it uses the same instance of that component. The docs therefore recommend to listen to $route changes or to use the beforeRouteUpdate navigation guard.
You need to watch the routes to update your page. see code below
watch:{
'$route' (to, from) {
this.goToProject()
// call your method here that updates your page
}
},
source dynamic route matching
Without reloading the whole page I need to reload the current route again (Only a component reload) in a vue app.
I am having a path in vue router like below,
{
path: "/dashboard",
name: "dashboard",
component: loadView("Dashboard"),
},
When user clicks on the Dashboard navigation item user will be redirected to the Dashboard page with vue router programmatic navigation
this.$router.push({ name: "dashboard" });
But when user already in the dashboard route and user clicks the Dashboard nav item again nothing happens. I think this is vue router's default behaviour. But I need to force reload the Dashboard component (Not to refresh the whole page).
I can't use beforeRouteUpdate since the router is not updated. Also I have tried the global before guards like beforeEach. But it is also not working.
How can I force reload the dashboard component without reloading the whole page?
It can be done in two ways.
1) Try doing vm.$forceUpdate(); as suggested here.
2) You can take the strategy of assigning keys to children, but whenever you want to re-render a component, you just update the key.
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
Every time that forceRerender is called, the prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and “reset” its state.
Not mentioned here, but as the offered solutions require a lot of additional work just to get the app to render correctly, which imo is a brittle solution.. we have just implemented another solution which works quite well..
Although it is a total hack.
if (this.$route.name === redirect.name) {
// this is a filthy hack - the vue router will not reload the current page and then have vue update the view.
// This hack routes to a generic page, then after this has happened the real redirect can happen
// It happens on most devices too fast to be noticed by the human eye, and in addition does not do a window
// redirect which breaks the mobile apps.
await this.$router.push({
name: RouteNames.ROUTE_REDIRECT_PLACEHOLDER
});
}
... now continue to do your normal redirect.
Essentially, redirect to a placeholder, await the response but then immediately continue to another page you actually wanted to move toward
Should I always be using <router-link> over <a> even if I am linking to something like a social media page outside the app?
<router-link> is intended for in-app links (e.g., to a page within your app). Use <a> for external links.
Router link : if we use router link the page will not reload but with <a> link the navigation occured through page refresh.
‘vue-router’ is a separate package to Vue and is installed by ‘webpack’.
Routes are set up in router/index.js
Import to Router from vue-router which is in node_modules.
import Router from 'vue-router'
... new Router...
creates an instance of Router which is an object with a property ‘routes’ which is an array of object. Each of those objects is a route with properties for ‘path’, ‘name’ which is what it is called and ‘component’, the component that is called when the route is used.
{
path: '/about',
name: 'About',
component: About
}
We need to import the component and we use the # which brings the path to the root of the project which is the ‘src’ directory.
import About from '#/components/About'
The component gets loaded into the root component App.vue using,
<router-view/>
and where that tag is in the App.vue. This would allow us to place a navbar, header and footer for example around this About component. So that when you go to …/about only the about component in the page changes and the whole page doesn’t refresh.
Next create a NavBar component and place it in the App.vue template.
This will work,
About
however in vue it should be done like this,
<router-link to="/about>About</router-link>
When <router-link> is rendered to the browser it becomes <a> so in the css we still reference it as ‘a’ not as ‘router-link’.
We can data bind using the ‘to’ attribute in <router-link>like this,
:to={ name: 'About'}
where ‘name’ is the property in the ‘routes’ array of object in the main.js routes. This makes the route dynamic.
Using this data bind to change to ‘/about’ to something else such as ‘abt’ you only need to change,
path: 'abt',
in the routes array of objects.
Consider about routes below:
/form/1
/form/2
/form/3
When the address is / and I click on /form/1 VueRouter loads my component inside router-view but when I'm in /form/1 by clicking on /form/2 nothing happens!
You need to watch for param changes in the $route and perform the correct action (e.g. making an ajax call to retrieve the information for the given id), so:
watch: {
'$route' (to, from) {
// React to route change
}
}
You may also use the beforeRouteUpdate guard (see: https://router.vuejs.org/en/essentials/dynamic-matching.html)
You can also add a unique key attribute to the router-view so that vue forcefully replaces the component instead of reusing it
<router-view :key="$route.path"></router-view>