Vue-router is changing the url but not re-rendering the component
Asked Answered
D

4

10

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.

Detailed answered 19/10, 2021 at 23:30 Comment(1)
please take a look on this: mokkapps.de/vue-tips/…Insupportable
S
18

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.

Softwood answered 19/10, 2021 at 23:51 Comment(2)
Thank you so much! I spent a week trying to figure out why my UI wouldn't work and then found this.Borg
I've tried both those approaches, and my data is like one "url request" behind. Ie. The url changes on first change, no change in the component, then I go to the next one, and it updates - but it's the last URL data.Abutting
B
2

This solution: https://mcmap.net/q/453793/-vue-router-is-changing-the-url-but-not-re-rendering-the-component 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:

  1. specify, which param you want to watch inside routes config:
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' //
        }
      },
    ],
  },
];
  1. Put it in your layout component:
<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.

Brethren answered 21/3, 2023 at 21:18 Comment(0)
D
2

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.

Derain answered 7/12, 2023 at 13:24 Comment(0)
K
0

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?',
      },
    ],
  },
];
Kelleekelleher answered 18/6 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.