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
});
}
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).
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)
}
},
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.
you can use just this code:
watch: {
$route(to, from) {
// react to route changes...
}
}
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!
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,
}
Related
I have one component in which I use axios to get data from API, it works and I can use this data in this component, but when I try to provide this data to another component, I dont get any data there.
Here is part of my code:
data() {
return {
theme: [],
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme = response.data.data));
},
},
mounted() {
this.getTheme();
},
and this is the second component:
<template>
<div class="project-wrapper">
<project-card
v-for="course in theme.courses"
:name="course.name"
:key="course.id"
></project-card>
</div>
</template>
<script>
import ProjectCard from "../components/ProjectCard.vue";
export default {
inject: ["theme"],
components: {
ProjectCard,
}
};
</script>
What is wrong with my code?
Second option in the link may help you
provide() {
return {
$theme: () => this.theme,
}
},
and
inject: ["$theme"],
computed: {
computedProperty() {
return this.$theme()
}
}
and
v-for="course in computedProperty.courses"
When you set provide to 'handle' theme it adds reactivity to the value of theme - i.e the empty array ([]).
If you modify the elements in this array, it will remain reactive - however if you replace the array then the reactivity is broken.
Instead of overwriting theme in the axios call, try adding the resulting data to it. For example:
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.push(...response.data.data));
}
You are passing theme to the child component as injected property.
See Vue.js Docs:
The provide and inject bindings are NOT reactive. This is intentional.
However, if you pass down an observed object, properties on that
object do remain reactive.
As inject bindings are not reactive, the changed value of theme will not be visible from inside of the child component (it will stay the same as if no axios call happened).
Solution 1
Pass the value to the child component as an observed object. It means that in in your getTheme() method you will not rewrite the whole property value (this.theme = ...) but only write into the object which is already stored in the property (this.theme.themeData = ...).
data() {
return {
theme: { },
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.themeData = response.data.data));
},
},
mounted() {
this.getTheme();
}
Solution 2
Alternatively you can pass the value to the child component using classical props which are always reactive.
I use vuex, and in my page module I set title and content, I arranged a destroyed method to reset them, If I click a different component there is no problem while resetting values but when I click a different static page, component is not destroyed and data is not updated. Is there a way to reset vuex state values for the same component.?
const state = {
page: {},
};
const getters = {
page(state) {
return state.page;
},
};
const mutations = {
setAPage (state, pPage) {
state.page = pPage
state.errors = {}
},
setCleanPage(state){
state.page = null
},
reset(state) {
const s = state();
Object.keys(s).forEach(key => {
state[key] = s[key];
});
console.log('state', state)
}
}
const actions = {
fetchAPage (context, payload) {
context.commit("setCleanPage");
const {slug} = payload;
return ApiService.get(`pages/${slug}/`)
.then((data) => {
context.commit("setAPage", data.data);
})
.catch((response) => {
context.commit("setError", response.data)
})
},
resetAPage(context){
context.commit("reset");
}
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
and in my component :
<script>
import { mapGetters, mapActions, mapMutations } from "vuex";
export default {
name: "Page",
computed: {
...mapGetters('pages', {page: 'page'}),
},
beforeRouteLeave (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.
console.log(to)
console.log(from)
console.log(next)
this.$store.dispatch('pages/resetAPage');
},
methods: {
...mapActions(['pages/fetchAPage']),
},
destroyed() {
this.toggleBodyClass('removeClass', 'landing-page');
this.$store.dispatch('pages/resetAPage');
},
created() {
this.$store.dispatch('pages/fetchAPage' , this.$route.params)
},
};
</script>
How can I reset or update data for the same component ?
Thanks
You can use this package for your resets - https://github.com/ianwalter/vue-component-reset
You can use a beforeRouteLeave guard in your component(s) when you want to catch the navigation away from the route where the component is being used (https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards)
The beforeRouteUpdate component guard is called only when the component was used in the current route and is going to be reused in the next route.
I would suggest you watch for that param change.
I don't know how you make use of your params, but you can pass them to your component as props and then add a watcher on them which would call your vuex reset action.
// in your router
// ... some routes
{
path: "/page/:id",
props: true, // this passes :id as prop to your component
component: Page
}
In you component
export default {
name: "Page",
props: ["id"], // your route param
computed: {
...mapGetters('pages', {page: 'page'}),
},
beforeRouteLeave (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.
console.log(to)
console.log(from)
console.log(next)
this.$store.dispatch('pages/resetAPage');
},
methods: {
...mapActions(['pages/fetchAPage']),
},
watch: {
id() { // watch the id and reset the page if it has changed or add additionnal logic as needed
this.$store.dispatch('pages/resetAPage');
}
},
destroyed() {
this.toggleBodyClass('removeClass', 'landing-page');
this.$store.dispatch('pages/resetAPage');
},
created() {
this.$store.dispatch('pages/fetchAPage' , this.$route.params)
},
};
I am trying to pass data through router. My code is working but it shows data in url. I don't want that like as POST method.url should like /data-list . Also I want to catch passing value from component. Here I did not use vuex . Actually my job is to show message that task is done based on this data. I am using Laravel for backend. Thanks in advance
1st component
axios.post("/request/data", dataform).then(function (resp) {
app.$router.push({ path: "/data-list/" + resp.data.success });
});
routes
{
path: '/data-list/:message?',
name: 'dataList',
component: dataList,
meta: {
auth: true
}
},
Another component. Here I want to catch
mounted() {
var app = this;
app.message = app.$route.params.message;
}
So if I understand correctly, you are fetching data in some component and then you are doing a router-push to dataList component.
You want to access the data in dataList component.
Since you always want the route to be /dataList, do this in your routes file
{
path: '/data-list', //removing dynamic tag.
name: 'dataList',
component: dataList,
meta: {
auth: true
}
},
Then in the component where you do router push, add a handleClick like so.
handleClick() {
let data = {
id: 25,
description: "pass data through params"
};
this.$router.push({
name: "dataList", //use name for router push
params: { data }
});
}
}
Then in your dataList component you can access the data passed in the mounted like so :
mounted() {
let data = this.$route.params.data;
console.log("data is", data);
}
Working implementation attached below.
You can push router data in router like this.
this.$router.push({
name: "dataList",
params: { data: resp.data },
});
and in the routes you can define your route as
{
path: "/dataList",
name: "dataList",
props: true,
meta: { title: "Data list" },
component: () => import("path to datalist component")
},
and in the DataList.vue you can use props to get the data
export default {
props:['data'],
mounted(){
alert(this.data);
}
}
I have a button that is being used to handle back button. I want to render this button in child components only so i added a V-IF condition.
<q-btn
flat
dense
round
v-go-back.single = true
v-if="currentRoute"
icon="arrow_back"
class="q-mx-md menu-icon"
/>
<script>
export default {
name: 'HeaderComponent',
data () {
return {
route: this.$router.currentRoute.path
}
},
computed: {
currentRoute () {
return this.route !== '/'
}
},
</script>
In home page, the button is not seen. But when i change the route to child component, button is still not seen. When i reload the page in child component button is seen.
And when i navigate back to home page , button is still seen. Basically, the V-IF condition is not reactive.
How can i make this reactive.
Any help is very much appreciated.
Regards
I faced a similar issue. I can't explain why is that but understand that data route not deeply watched so route not updating with $router change.
How I solve it. I called a $route watch function like this
<script>
export default {
name: 'HeaderComponent',
data () {
return {
route: this.$router.currentRoute.path
}
},
watch: {
// call again if the route changes
'$route': function(to, from) {
this.route = to.path
}
},
computed: {
currentRoute () {
return this.route !== '/'
}
},
</script>
I think this will help you.
I have a router-view object made in this way
export default new Router({
linkExactActiveClass: 'active', // active class for *exact* links.
mode: 'history',
routes: [
{
path: '/admin',
name: 'dashboard',
component: Dashboard
},
{
path: '/company',
name: 'company',
component: ObjectList,
props: {
url: '/web/company',
idColumn: 'companyId'
}
}
]
})
when i hit /company link the router correctly sends me to the ObjectList component
In this page I have a simple pagination component made in this way
<template>
<div class="overflow-auto">
<b-pagination-nav
align="center"
:link-gen="linkGen"
:number-of-pages="totalPages"
use-router
></b-pagination-nav>
</div>
</template>
<script>
export default {
data() {
return {
};
},
name: "pagination",
props: {
current: Number,
url: String,
totalPages: Number
},
methods: {
linkGen(pageNum) {
return pageNum === 1 ? "?" : `?page=${pageNum}`;
}
}
};
</script>
Now, the second page, for instance, correctly has this url:
/company?page=2
and if i click on it, i go to the correct url. the problem is how to tell the router-link that on pressing the second page button another call to the backend must be done?
I thought the use-router option should catch this link and make the request as when i click the /company link in my standard navigation bar, but obviously not..
You can receive query props using this.$route.query
You should define computed prop to receive:
computed: {
page () {
return this.$route.query.page || 1
}
}
Now you can watch this property and on every change call the backend:
watch: {
page () {
// call backend
}
}
Also consider calling backend on created event hook once computed property page is set (depends on your needs):
created () {
// call backend by passing this.page
}
Feel free to ask more if something unclear or if I misunderstood you.
You can also handle every url query param change with this piece of code:
watch: {
'$route.query': {
immediate: true,
handler(newVal) {
console.log(newVal)
// make actions with newVal.page
}
}
}
Just want to add an improvement related to #Ignas Damunskis answer that is already great.
If you want to listen to changes on created and when the specific property changes (in this case the query parameter)
You don't need the created method, you can do it all in one on the watch method, like below:
watch: {
page: {
handler: function (val, oldVal) { /* ... */ },
immediate: true
}
}
Hope it helps :)
I wasn't able to get this working with vue-router however
When parent component is created() {...}
check for if (this.$route.query.order} exists
then set this.viewOrder to true, and load the child component
and pass props to child component
you can do this also by vue route's dynamic-matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
created () {
// call backend
},
watch: {
$route(to, from) {
// call backend with (to.params.xxxx)
}
}