Here's a part of my Vue template:
<ul>
<li v-for="friend in user.friends">
<span v-if="checkIfNew(friend.id)"> ... </span>
</li>
</ul>
Basically, friends is an array of objects, and I want to display the span element, if we have new messages from any of them. That's what checkIfNew()
does. It checks whether the friend's id is in the unreadIds
array (it contains ids of friends, who sent a message)
This array is being updated in a different method, but, here's the problem: v-if
doesn't react to the changes.
Here's a part of the script section:
data(){
return {
unreadIds: []
}
},
methods:{
checkIfNew(id){
if(id in this.unreadIds) return true
else return false
}
},
puted:{
user(){
return this.$store.getters.user;
}
}
Does anyone have any ideas what am I doing wrong?
Here's a part of my Vue template:
<ul>
<li v-for="friend in user.friends">
<span v-if="checkIfNew(friend.id)"> ... </span>
</li>
</ul>
Basically, friends is an array of objects, and I want to display the span element, if we have new messages from any of them. That's what checkIfNew()
does. It checks whether the friend's id is in the unreadIds
array (it contains ids of friends, who sent a message)
This array is being updated in a different method, but, here's the problem: v-if
doesn't react to the changes.
Here's a part of the script section:
data(){
return {
unreadIds: []
}
},
methods:{
checkIfNew(id){
if(id in this.unreadIds) return true
else return false
}
},
puted:{
user(){
return this.$store.getters.user;
}
}
Does anyone have any ideas what am I doing wrong?
Share Improve this question edited Jan 7, 2020 at 19:21 Koanna asked Jan 7, 2020 at 18:35 KoannaKoanna 631 gold badge1 silver badge6 bronze badges 5-
Show us how you update
unreadIds
. – Ohgodwhy Commented Jan 7, 2020 at 18:41 - @Ohgodwhy it's in the apollo $subscribe part, in the result: this.unreadIds.push(...). I know the array itself is updating correctly. – Koanna Commented Jan 7, 2020 at 18:46
-
1
When you say "v-if doesn't react to the changes.", that's not necessary since it's inside the loop. What you need is for the loop to react to the change, which means make a puted, something like
friends() { return this.user.friends }
and base the loop on that. – Richard Matsen Commented Jan 7, 2020 at 18:57 - @eric99 thanks x2 unreadIds are ids of friends, who sent a new message. I'll edit the question, it really is confusing – Koanna Commented Jan 7, 2020 at 19:19
- @RichardMatsen Messages don't affect friends. So when the new message arrives, friends don't change. So the loop is based on friends, but I want it to also react to unreadIds... – Koanna Commented Jan 7, 2020 at 19:26
3 Answers
Reset to default 5You want to leverage Vue's reactivity system as the previous answers do not.. they will eventually open you up to inexplicable problems that aren't easily debuggable.
Rather than invoking a method in a v-for
(which I guarantee will bee problematic for you in the future), you should declare a puted list that contains (or does not contain) the items you want rendered, something like this:
data(){
return {
unreadIds: []
}
},
puted:{
user(){
return this.$store.getters.user;
},
NewFriends() {
return this.user.friends.filter(friend => this.unreadIds.includes(friend.id));
}
}
Your markup would then just be:
<ul>
<li v-for="friend in NewFriends">
<span > ... </span>
</li>
</ul>
And Vue's reactivity system would handle any changes to data dependencies for NewFriends
.
You don't want to ever use method calls in a template because method calls are only guaranteed to be invoked once (the same applies to functions that e from puted methods...).
If you find yourself trying to trigger re-renders and dependency checks manually, you will want to re-think your design.
ETA: The only time you will ever want to invoke a function of any kind in a template is to respond to events.
id in this.unreadIds
doesn't do what you think it does. See the docs for the in operator. It will return true
if the object has the value as a property. So if this.unreadIds
had 3 items and you had an id of 1
, then the in
operator will return true
because 1
is a property of the array (this.unreadIds[1]
exists).
Instead, you should use includes.
Try rewriting your method like this:
checkIfNew(id) {
return this.unreadIds.includes(id);
}
Here's a working version of the ponent that updates the list without the Vuex store code:
<ul>
<li v-for="friend in user.friends" :key="friend.ids">
<span v-if="checkIfNew(friend.id)">{{ friend.name }}</span>
</li>
</ul>
export default {
data() {
return {
unreadIds: [5],
user: {
friends: [
{ id: 1, name: 'joe' },
{ id: 5, name: 'nancy' },
{ id: 9, name: 'sue' },
],
},
};
},
created() {
setTimeout(() => this.unreadIds.push(9), 2000);
},
methods: {
checkIfNew(id) {
return this.unreadIds.includes(id);
},
},
};
Just to prove here that David was correct all along, I put this code in a runable snippet, and cannot find any fault...
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
unreadIds: [],
};
},
created() {
setTimeout(() => this.unreadIds.push(9), 2000);
},
methods: {
checkIfNew(id) {
// if(id in this.unreadIds) return true
// else return false
return this.unreadIds.includes(id);
},
},
puted: {
user(){
return {
friends: [
{ id: 1, name: 'joe' },
{ id: 5, name: 'nancy' },
{ id: 9, name: 'sue' }
]
}
}
}
})
<script src="https://cdnjs.cloudflare./ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app">
<ul>
<li v-for="friend in user.friends" >
<span v-if="checkIfNew(friend.id)">{{ friend.name }}</span>
</li>
</ul>
</div>
The sample above is a bit closer to the original question
- user is a puted not data
- unreadIds is initially empty
Upvote from me!
I'll have a stab at it - someone posted this earlier and deleted it. You need checkIfNew()
to be a puted not a method for reactivity in the template.
Since you need to pass in the id, the puted needs to return a function.
data(){
return {
unreadIds: []
}
},
puted:{
user(){
return this.$store.getters.user;
},
checkIfNew(){
return (id) => {
return this.unreadIds.includes(id);
}
}
}
As David Weldon says, you should ideally change the array immutably - and probably why ohgodwhy asked the original question.