Watch $route.params.id does not trigger re-render of Vue component - vue.js

I have a Post component which displays user posts. The URL/Route to get to a post is like:
http://localhost:8080/123-todays-bike-ride with 123 being the PostID param in the route.
In my Post.vue component I have the following code:
<template>
<div>
<h1>{{Post.PostTitle}}</h1>
<p>{{Post.Content}}</p>
</div>
</template>
<script>
export default {
name: "Post",
watch:{
'$route.params.PostID': function() {
this.getPost(); // does not seem to be triggered?
}
},
computed:
{
Post() {
return this.$store.getters.getPost
}
},
serverPrefetch() {
return this.getPost(); // to do with SSR
},
mounted() {
if (this.Post == null || !this.Post.length) {
this.getPost();
}
},
methods: {
getPost() {
return this.$store.dispatch('loadPost', {PostID: this.$route.params.PostID})
}
}
}
</script>
The problem is that even if I navigate from http://localhost:8080/123-todays-bike-ride to say http://localhost:8080/999-swimming then the URL in the browser address bar changes but the content of the Post view does not change – it remains with the same content as the 123-todays-bike-ride post.
Clearly the watch: { '$route.params.PostID'... bit is not working, but why and how to solve it?

You can try and make it watch deep:
watch: {
"$route.params.PostID": {
handler: function(value) {
console.log(value);
},
deep: true,
immediate: true,
},
},
Also another way of doing this by re-rendering a component without using watch:
by adding :key="$route.params.PostID" to the Post component like:
inside ParentComponent.vue
<template>
<Post :key="$route.params.PostID" />
</template>

Related

Vue/Vuex dynamic component not changing

I have a vuex store of "nodes". Each one has a type of Accordion or Block.
{
"1":{
"id":1,
"title":"Default title",
"nodes":[],
"type":"Block"
},
"2":{
"id":2,
"title":"Default title",
"nodes":[],
"type":"Accordion"
}
}
When I use the type to create a dynamic component it works great:
<ul>
<li v-for="(node, s) in nodes" :key="parentId + s">
<component :is="node.type" :node="node" :parent-id="parentId"></component>
</li>
</ul>
But when I change it, nothing happens in the view layer:
convert(state, { to, id }) {
state.nodes[id].type = to;
Vue.set(state.nodes[id], "type", to);
},
I even use Vue.set. How can I make this update?
It updates immediately if I then push another node into the array.
CodeSandbox:
https://codesandbox.io/s/romantic-darwin-dodr2?file=/src/App.vue
The thing is that your getter will not work, because it's not pure: Issue. But you can use deep watcher on your state instead:
<template>
<div class="home">
<h1>Home</h1>
<Sections :sections="nodesArr" :parent-id="null"/>
</div>
</template>
<script>
// # is an alias to /src
import Sections from "#/components/Sections.vue";
import { mapState } from "vuex";
export default {
name: "home",
components: {
Sections
},
data: () => {
return {
nodesArr: []
};
},
computed: {
...mapState(["nodes", "root"])
},
watch: {
root: {
handler() {
this.updateArr();
},
deep: true
}
},
mounted() {
this.updateArr();
},
methods: {
updateArr() {
this.nodesArr = this.root.map(ref => this.nodes[ref]);
}
}
};
</script>

Vuejs watcher not reacting to prop's change

I have a simple .vue component in which there is one prop request and a watcher to watch this prop. If request is true then I call a method.
The problem is, my watcher is not reacting to the changes.
I have tried to make the code below work.
foobar.vue
<template><div> ... </div></template>
<script>
export default {
// ...
props: {
request: {
type: Boolean,
required: false,
default: false
}
},
watch: {
request (state) {
if(state) {
// run some method
}
}
}
}
</script>
I now call the component as (after requiring it)
<template>
<foobar :request="access"> </foobar>
<button #click="access = !access"> {{ access ? 'Turn off' : 'Turn on'}} </button>
</template>
<script>
export default {
data () {
return { access: false }
},
components: { foobar }
}
</script>
Set the watcher to immediate.
watch: {
request :
immediate:true,
handler(state) {
if(state){
// run some method
}
}
}
}
}

Vuejs get current route name and display on the menu

I'm using the vue-menu component, and using it to toggle between different routes. After I switch to another route I want to display the current route name on the dropdown header like so:
I've tried using the life-cycle methods such as beforeCreate , created, mounted... etc but none of them are being called. How can I achieve the desired result?
You should use the "watch"
data() {
return {
routeName: null,
};
},
watch: {
'$route': 'currentRoute'
},
methods: {
currentRoute() {
this.$nextTick(() => {
this.routeName = this.$route.name
});
}
},
<script>
export default {
computed:{
routeName(){
return this.$route.name
},
}
};
</script>
<template>
<p>Current Route is {{$route.name}}</p>
</template>
-------------------OR--------------
<script>
export default {
computed:{
routePath(){
return this.$route.path
},
}
};
</script>
<template>
<p>Current Route is {{$route.path}}</p>
</template>
This answer is for vue2.
Also, created lifecycle can also be used by directly returning the route example below
created() {
return this.$route.name
},

VueJs Nested props coming through undefined

I am trying to access an array which is part of a prop (event) passed into a component, but when in created() or mounted() the array part of the event prop (the rest is fine) comes through as undefined.
As can be seen below, when I inspect the props in the vue chrome plugin, the registration_fields are there.
I can add a watcher to the event prop and can access the registration_fields that way, but this seems very awkward to have to do this to access already passed in data.
This is from the Chrome vue inspector:
event:Object
address1_field:"Some Address 1"
address2_field:"Some Address 2"
approved:true
registration_fields:Array[1]
This is what part of my vue file looks like:
export default {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
},
watch: {
event() {
this.regFields = this.event.registration_fields //Can access it here
});
}
}
}
I am using Vue 2.4.4
This is how the component is called:
<template>
<tickets v-if="event" :event="event"></tickets>
</template>
<script>
import tickets from './main_booking/tickets.vue'
export default {
created() {
var self = this;
this.$http.get('events/123').then(response => {
self.event = response.data
}).catch(e => {
alert('Error here!');
})
},
data: function () {
return {event: {}}
},
components: {
tickets: tickets
}
}
</script>
Thank you
It actually works fine without the watcher.
new Vue({
el: '#app',
data: {
event: undefined
},
components: {
subC: {
props: ['event'],
data() {
return {
regFields: []
}
},
created() {
this.regFields = this.event.registration_fields // Undefined here!
}
}
},
mounted() {
setTimeout(() => {
this.event = {
registration_fields: [1, 3]
};
}, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c v-if="event" :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>
If, as Belmin Bedak suggests in the comment below, event is populated asynchronously, it comes in as undefined because it's undefined. In that case, you need a watcher, or, somewhat more elegantly, use a computed:
new Vue({
el: '#app',
data: {
event: {}
},
components: {
subC: {
props: ['event'],
computed: {
regFields() {
return this.event.registration_fields;
}
}
}
},
// delay proper population
mounted() {
setTimeout(() => { this.event = {registration_fields: [1,2,3]}; }, 800);
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<sub-c :event="event" inline-template>
<div>
{{regFields}}
</div>
</sub-c>
</div>

V-model with props & computed properties

I have a checkbox component that tracks whether or not an item has been saved by the user as a favorite. This information is passed in as a prop.
Because we can't/shouldn't mutate props passed in from a parent component, I am using v-model on a computed property.
<template>
<input class="favorite" type="checkbox" v-model="checked">
</template>
<script>
module.exports = {
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
};
</script>
The parent component controls sending requests to the favorites api & updating the state of each entity if/when the request is successful.
<template>
<input-favorite
#update:favorite="toggleFavorite"
:favorite="entity.favorite"
></input-favorite>
</template>
<script>
module.exports = {
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
this.$store.dispatch('postFavorite', { id: this.entity.id, name: this.entity.name });
} else {
this.$store.dispatch('deleteFavorite', this.entity.id);
}
}
}
};
</script>
If the request fails, however, is it possible to prevent the checkbox from getting checked in the first place? Both this.favorite and this.checked stay in sync, but the state of the checkbox does not.
Because the data & props stay correct, I'm also having trouble figuring out how I could trigger a re-render of the checkbox to get it back to the correct state.
I suspect the problem is that favorite never changes, so Vue doesn't see a need to update. You should update it to true upon receiving the checked value (so state is consistent) and then update it again to false when the request fails.
Vue.component('inputFavorite', {
template: '#input-favorite',
props: ['favorite'],
computed: {
checked: {
get: function getChecked() {
return this.favorite;
},
set: function setChecked(newVal) {
this.$emit('update:favorite', newVal);
}
}
}
});
new Vue({
el: '#app',
data: {
entity: {
favorite: false
}
},
methods: {
toggleFavorite: function toggleFavorite(val) {
if (val) {
console.log("Post");
this.entity.favorite = true;
// Mock up a failure
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
} else {
console.log("Delete");
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<template id="input-favorite">
<input class="favorite" type="checkbox" v-model="checked">
</template>
<div id="app">
<input-favorite #update:favorite="toggleFavorite" :favorite="entity.favorite"></input-favorite>
</div>
The way you have set this up lends itself to the recently-reintroduced .sync modifier, which would simplify your HTML a bit:
<input-favorite :favorite.sync="entity.favorite"></input-favorite>
Then you do away with toggleFavorite and instead add a watch:
watch: {
'entity.favorite': function (newValue) {
console.log("Updated", newValue);
if (newValue) {
setTimeout(() => {
console.log("Failed");
this.entity.favorite = false;
}, 250);
}
}
}