user1627827
user1627827

Reputation: 695

Update VueJs component on route change

Is there a way to re-render a component on route change? I'm using Vue Router 2.3.0, and I'm using the same component in multiple routes. It works fine the first time or if I navigate to a route that doesn't use the component and then go to one that does. I'm passing what's different in props like so

{
  name: 'MainMap',
  path: '/',
  props: {
    dataFile: 'all_resv.csv',
    mapFile: 'contig_us.geo.json',
    mapType: 'us'
  },
  folder: true,
  component: Map
},
{
  name: 'Arizona',
  path: '/arizona',
  props: {
    dataFile: 'az.csv',
    mapFile: 'az.counties.json',
    mapType: 'state'
  },
  folder: true,
  component: Map
}

Then I'm using the props to load a new map and new data, but the map stays the same as when it first loaded. I'm not sure what's going on.

The component looks like this:

data() {
  return {
    loading: true,
    load: ''
  }
},

props: ['dataFile', 'mapFile', 'mapType'],

watch: {
    load: function() {
        this.mounted();
    }
},

mounted() {
  let _this = this;
  let svg = d3.select(this.$el);

  d3.queue()
    .defer(d3.json, `static/data/maps/${this.mapFile}`)
    .defer(d3.csv, `static/data/stations/${this.dataFile}`)
    .await(function(error, map, stations) {
    // Build Map here
  });
}

Upvotes: 33

Views: 62014

Answers (7)

Andrey
Andrey

Reputation: 2068

Make wrapper of your component which will force re-loading the original component by usage of 'key' attribute. Vue is not reusing components/parts of DOM if there is unique 'key' value assigned.

For example, you have UserList component which you want to completely reload (with invocation of onMounted) every time something changes in route.

Make wrapper UserListRefresh with the following content

<script lang="ts" setup>
    import { useRoute } from "vue-router";
    import UsersList from "./UsersList.vue";
    const route=useRoute();
</script>

<template>
    <UsersList :key="route.fullPath"></UsersList>
</template>

And now assign this UserListRefresh to the route instead of original UserList. That will do the trick, navigate to same route with different :groupId part and you will see that component is being rebuilt.

{
    path: "/administrative/users/:groupId",
    component: UsersListRefresh,
    name: 'UsersList',
    meta: {
        title: 'User List of Group',
    },
},

Upvotes: 1

Pijush Gupta
Pijush Gupta

Reputation: 41

I was having the same issue, but slightly different. I just added a watch on the prop and then re-initiated the fetch method on the prop change.

import { ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import Page  from './content/Page.vue';
import Post  from './content/Post.vue';

const props = defineProps({ pageSlug: String });
const pageData = ref(false);
const pageBodyClass = ref('');

function getPostContent() {
    let postRestEndPoint = '/wp-json/vuepress/v1/post/' + props.pageSlug;
    fetch(postRestEndPoint, { method: 'GET', credentials: 'same-origin' })
        .then(res => res.json())
        .then(res => {
            pageData.value = res;
            
        })
        .catch(err => console.log(err));
}
getPostContent();

watch(props, (curVal, oldVal) => {
    getPostContent();
});
watch(pageData, (newVal, oldVal) => { 
    if (newVal.hasOwnProperty('data') === true && newVal.data.status === 404) {
        pageData.value = false;
        window.location.href = "/404";
    }

    
    
});

router - index.js

       {
            path: "/:pageSlug",
            name: "Page",
            component: Page,
            props: true,
        },
        {
            path: "/product/:productSlug",
            name: "Product",
            component: Product,
        },
        {
            path: "/404",
            name: "404",
            component: Error404,
        }

Upvotes: 0

Bert
Bert

Reputation: 82439

The alternate solution to this question handles this situation in more cases.

First, you shouldn't really call mounted() yourself. Abstract the things you are doing in mounted into a method that you can call from mounted. Second, Vue will try to re-use components when it can, so your main issue is likely that mounted is only ever fired once. Instead, you might try using the updated or beforeUpdate lifecycle event.

const Map = {
  data() {
    return {
      loading: true,
      load: ''
    }
  },
  props: ['dataFile', 'mapFile', 'mapType'],
  methods:{
    drawMap(){
        console.log("do a bunch a d3 stuff")
    }
  },
  updated(){
    console.log('updated')
    this.drawMap()
  },
  mounted() {
    console.log('mounted')
    this.drawMap()
  }
}

Here's a little example, not drawing the d3 stuff, but showing how mounted and updated are fired when you swap routes. Pop open the console, and you will see mounted is only ever fired once.

Upvotes: 17

you can use just this code:

  watch: {
    $route(to, from) {
      // react to route changes...
    }
  }

Upvotes: 2

mdmostafa
mdmostafa

Reputation: 852

Yes, I had the same problem and solved by following way;

ProductDetails.vue

 data() {
    return {
       ...
       productId: this.$route.params.productId,
       ...
     };
   },
 methods: {
  ...mapActions("products", ["fetchProduct"]),
  ...
 },
created() {
    this.fetchProduct(this.productId);
...
}

The fetchProduct function comes from Vuex store. When an another product is clicked, the route param is changed by productId but component is not re-rendered because created life cycle hook executes at initialization stage.

When I added just key on router-view on parent component app.vue file

app.vue

<router-view :key="this.$route.path"></router-view>

Now it works well for me. Hopefully this will help Vue developers!

Upvotes: 0

Irfandy J.
Irfandy J.

Reputation: 1444

UPDATE --- 3 July, 2019

I found this thing on vue-router documentation, it's called In Component Guards. By the description of it, it really suits your needs (and mine actually). So the codes should be something like this.

export default () {
  beforeRouteUpdate (to, from, next) {
    // called when the route that renders this component has changed,
    // but this component is reused in the new route.
    // For example, for a route with dynamic params `/foo/:id`, when we
    // navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
    // will be reused, and this hook will be called when that happens.
    // has access to `this` component instance.
    
    const id = to.params.id
    this.AJAXRequest(id)
    next()
  },
}

As you can see, I just add a next() function. Hope this helps you! Good luck!


Below is my older answer.
Only saved for the purpose of "progress"

My solution to this problem was to watch the $route property.
Which will ended up you getting two values, that is to and from.

watch: {
  '$route'(to, from) {
    const id = to.params.id
    this.AJAXRequest(id)
  }
},

Upvotes: 22

Matt Hagemann
Matt Hagemann

Reputation: 1506

You may want to add a :key attribute to <router-view> like so:

<router-view :key="$route.fullPath"></router-view>

This way, Vue Router will reload the component once the path changes. Without the key, it won’t even notice that something has changed because the same component is being used (in your case, the Map component).

Upvotes: 100

Related Questions