Reputation: 1345
First of all I've checked and history mode is turned on, I invoke vue-router
like so:
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: routes,
});
The current route I am on is /page/item/8
, and I am redirecting to /page/item/9
. Currently the url changes but the page does not re-render leaving me on the same view I had before.
This redirection is done like so:
this.$router.push({ name: 'PageItem', params: { itemId: '9' }}).catch(err => {
// Stop Vue.router throwing an error if a user clicks on a notification with the same route.
if (err.name !== 'NavigationDuplicated') {
this.$logger.debug.log(err);
}
});
The route in question like so:
import PageWrapper from '@/layouts/PageWrapper';
export default [
{
path: '/page',
name: 'page',
component: PageWrapper,
children: [
... Other Routes ...
{
component: () => import(/* webpackChunkName: "page-item" */'@/pages/PageItem'),
name: 'PageItem',
path: '/item/:itemId/:view?',
},
],
},
];
Any ideas, I've tweaked the code to remove identifying code so apologies if it looks a bit odd.
Upvotes: 10
Views: 20000
Reputation: 7687
As mentioned in the chosen answer, adding a key to router-view will force all components to be re-rendered which isn't exactly ideal in some cases.
A different solution which seems to work is one which utilises defineAsyncComponent to lazy load specific components when rendered:
// It needs to be imported from vue:
import { defineAsyncComponent } from 'vue';
import PageWrapper from '@/layouts/PageWrapper';
export default [
{
path: '/page',
name: 'page',
component: PageWrapper,
children: [
... Other Routes ...
{
// Use it on specific routes like so:
component: defineAsyncComponent(() => import(/* webpackChunkName: "page-item" */'@/pages/PageItem')),
// Compare this usage against the existing import declaration:
//component: () => import(/* webpackChunkName: "page-item" */'@/pages/PageItem'),
name: 'PageItem',
path: '/item/:itemId/:view?',
},
],
},
];
Upvotes: 0
Reputation: 1589
The accepted solution works well, but all child component is reloaded. So if you use a :key
on the <router-view>
on the main component, it may be too overkill if you have other routes like /xxx/:id
and don't want to reload them.
The ideal solution would be to watch parameter change, then re-initialize state from this. But in some components, handling this logic makes it too complicated (i.e if there is a lot of variables or refs to re-initialize).
So what I did is to wrap my component I want to reset in a component, and the :key
only on this wrapper.
I have a ReloadOnRouteChange.vue
component with only:
<template>
<router-view :key="$route.path"></router-view>
</template>
And in my router, I wrap my component:
const routes: RouteRecordRaw[] = [
{
name: 'user',
path: '/users/:id',
component: User,
},
like this:
const routes: RouteRecordRaw[] = [
{
+ path: '/',
+ component: ReloadOnRouteChange,
+ children: [
+ {
name: 'user',
path: '/users/:id',
component: User,
+ },
+ ],
},
This way, only the User
component will be fully reloaded when only the id change in the route.
Upvotes: 2
Reputation: 1056
This solution: https://stackoverflow.com/a/69638787/1330193 not works well in all situations, because it will redraw full components tree, and rebuild all components context. It means, that every time when you change :view
parameter, you will make an additional API request for item with :itemId
.
For example, if you want to save scope of parent route (in your case: /page/item/:itemId
) and only child routes should to be redrawn (in your case: /page/item/:itemId/:view?
), you can use this solution:
import PageWrapper from '@/layouts/PageWrapper';
export default [
{
path: '/page',
name: 'page',
component: PageWrapper,
children: [
... Other Routes ...
{
component: () => import(/* webpackChunkName: "page-item" */'@/pages/PageItem'),
name: 'PageItem',
path: '/item/:itemId/:view?',
meta: {
watchParam: 'itemId' //
}
},
],
},
];
<router-view :key="$route.params[$route.meta.watchParam]">
So with that, router will re-render route's component only when itemId
parameter changed.
Anyway, it's more declarative way to do that than watch component's props. In my opinion, it is a common behaviour, and should be implemented in vue-router library in more native way.
Upvotes: 3
Reputation: 34306
Only the itemId
route param is changing. In this situation, since the same route components are being used before and after the transition, vue-router will reuse the existing route components; they don't get destroyed and recreated. So if you're loading data in the component created
or mounted
hooks then they won't be called again after the route changes. Instead you need to use beforeRouteUpdate
or watch $route
for changes.
If you want to force the component to be destroyed and recreated (not recommended) then you need to key
the <router-view>
on the route:
<router-view :key="$route.fullPath">
Read the Data Fetching guide for more information.
Upvotes: 18