Infinite loop with v-for inside a v-for - vue.js

Can someone help me find out how I can achieve a loop inside a loop to print out a report of people and peoples annual leave.
Basically I have this:
<div v-for="user_list in user_list_filtered()">
<div class="user_heading"><h2>{{ user_list.first_name }}</h2></div>
<div class="report_content" v-for="user_leave in user_leave_filtered(user_list['.key'])">
<div class="first_date_content">
{{ user_leave.start_time | formatDate }}
</div>
<div class="days_taken_content">
{{ checkLSL(user_leave.hours, user_leave.type, false) }}
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
{{ getTotalLeave() }}
</div>
</div>
</div>
So this iterates through a Firebase DB and gets all of the users, it then loops through each user one by one and using the [.key] finds that users leave and loops through all of the leave as it outputs. Then onto the next user and there leave.
While this works it will continue to loop infinitely and says in my console.
vue.esm.js?efeb:591 [Vue warn]: You may have an infinite update loop in a component render function.
Can someone explain maybe a better way to do this without the infinite loop or a solution to avoid it constantly looping?
Thanks
EDIT
Filter functions
user_leave_filtered(userPassed) {
var self = this
return this.userLeave.filter(function(i) {
if (i.users_id === userPassed &&
((i.start_time >= self.getUnix(self.firstDate) && i.start_time <= self.getUnix(self.lastDate)) ||
(self.firstDate === null || self.firstDate === '' || self.lastDate === null || self.lastDate === ''))) {
return true
} else {
return false
}
})
},
user_list_filtered() {
var self = this
return this.userList.filter(function(i) {
var passed = false
if (self.userToShow === i['.key'] || self.userToShow === 'All') {
// Track whether to filter out this leave or not
self.userLeave.forEach(function(element) {
if (element.users_id === i['.key']) {
passed = true
}
})
}
return passed
})
},

The cause was a function (checkLSL()) I was using to calculate some totals through the loops. I have some info on how to fix and seems to be working fine.
Thanks

Related

How to do summation in Vue

I'm quite new to coding (less than 3months old) and I'm currently trying to learn vue.
I'm trying out this simple exercise of doing a basic shopping cart and I want to get the total of all the product amounts. Here is my code:
HTML
<template>
<div class="product" #click="isMilkshown = true">{{ productList[0].name }} $ {{ productList[0].amount }}</div>
<div class="product" #click="isFishshown = true">{{ productList[1].name }} $ {{ productList[1].amount }}</div>
<div class="product" #click="isLettuceshown = true">{{ productList[2].name }} $ {{ productList[2].amount }}</div>
<div class="product" #click="isRiceshown = true">{{ productList[3].name }} $ {{ productList[3].amount }}</div>
<!-- Cart -->
<div class="main-cart">
<div>Cart</div>
<div class="main-cart-list" v-for="product in productList" :key="product">
<div v-if="showProduct(product.name)">{{ product.name }} $ {{ product.amount }}</div>
</div>
<div>Total: 0</div>
</div>
</template>
JS
export default {
data() {
return {
productList: [
{ name: "Milk", amount: 10 },
{ name: "Fish", amount: 20 },
{ name: "Lettuce", amount: 5 },
{ name: "Rice", amount: 2.5 }
],
isMilkshown: false,
isFishshown: false,
isLettuceshown: false,
isRiceshown: false
}
},
methods: {
showProduct(name) {
if (name === "Milk" && this.isMilkshown === false) {
return false
} else if (name === "Fish" && this.isFishshown === false) {
return false
} else if (name === "Lettuce" && this.isLettuceshown === false) {
return false
} else if (name === "Rice" && this.isRiceshown === false) {
return false
} else {
return true
}
}
}
}
I want to replace the "zero" in Total with the sum of all the product amounts when a product is clicked. Hope someone can help me, thanks!
You would use a computed function.
https://v2.vuejs.org/v2/guide/computed.html
In Vue, computed functions watch all the reactive variables referenced within them and re-run to update the returned value when any of those variables change.
Simply create a computed function that loops over each productList item and sums up the amount then returns it.
You can reference this answer to learn how to sum using reduce or for a standard example with a loop Better way to sum a property value in an array
Also, you can use a v-for loop on your
<div class="product" #click="isMilkshown = true">{{ productList[0].name }} $ {{ productList[0].amount }}</div>
component so that you don't have duplicated code:
<div v-for="item in productList" key="item.name" class="product">{{ item.name }} $ {{ item.amount }}</div>
This would create one of each of those elements for each item in your productList variable.
You would then need to re-write the click handler to be dynamic too.
Lastly, you can also convert your big if/else-if chained method into a computed function too so that you watch for changes in that.
To do that, you'd make the computed return an object like this:
{
Milk: true,
Fish: false
...
}
You can put the key as the name so that in your loop you can reference the computed property like this enabledItems[item.name] to get the true/false.

dynamically show and hide button according to computed property in vuejs

i want to show delete button for each comment that a particular user made, but i am unable to do it, i am using v-bind to disable delete buttton for others comment, so that user cant delete others comment, but its still visible for all the comments i.e (for other users as well ). suppose i am a user i comment 3 times then delete button should show on my comments only not on others comment. can some one please help me to achieve this?
My code snippet is below
<div class="comment-list" v-for="comment in comments" :key="comment._id">
<div class="paragraph" :class="[name, onCaretButtonClick ? 'paragraph' : 'hide']">
<span class="names" id="comment-desc">
<label class="comm-name">{{ comment.name }}</label>
<button class="like-btn" id="like-comment"><i class="fas fa-heart"></i></button>
<label> {{ comment.likes + ' likes' }} </label>
<button v-bind:disabled="isCommentIdMatched" v-on:click="deleteComment(comment.userId)"
class="del-btn">delete</button>
</span>
<p>{{ comment.comment }}</p>
</div>
</div>
below is deleteComment function and computed properties that i am using
computed: {
comments() {
return store.state.comments
},
getUserId() {
return store.state.user._id
},
isCommentIdMatched() {
let comments = store.state.comments
let value = false
if(comments) {
comments.forEach((comment) => {
if(comment.userId.match(this.getUserId)) {
value = true
}
})
return value
}
else {
return value
}
}
},
methods: {
deleteComment: function(commentUserId) {
let userId = store.state.user._id
if(commentUserId.match(userId)) {
console.log('yes matched')
}
},
}
there is no need to write isCommentIdMatched property, instead you can use change some lines.
add v-bind="comments" in below line
<div v-bind="comments" class="comment-list" v-for="comment in comments" :key="comment._id">
and add v-if="comment.userId && comment.userId.match(getUserId)" in below button
<button v-if="comment.userId && comment.userId.match(getUserId)" v-on:click="deleteComment(comment.userId)" class="del-btn">delete</button>

Infinite loop warning in vue table rendering

I tried calling a function while rendering a table, and based on condition in the function i assigned that value and displayed it using string interpolation, but i am getting infinite loop error.
Below is url for code
jsfiddle.net/amit_bhadale/5kct0au1/2/
Below is function
checkData(day, time){
let that = this
let result = this.serverData.some(a=>{
if(a.Day === day && a.Time === time){
that.cellId = a.id
// This is giving infinite loop error
// if i chnage it to this then it works
// that.cellId = 'Some normal string'
}
return (a.Day === day && a.Time === time)
})
return result
}
HTML part
<table>
<tr v-for="(time, i) in time" :key="i">
<td v-for="(day, j) in day" :key="j">
<span v-if="checkData(day, time)">
</span>
<span v-else>
No data in this cell
</span>
</td>
</tr>
</table>
Dont update props multiple times with different values within in render cycle.
To seperate those you can just put it into a single component:
e.g.:
{
props: ['time', 'day', 'serverData'],
computed: {
cellValue(){
let val = "No data in this cell"
this.serverData.some(a=>{
if(a.Day === this.day && a.Time === this.time){
val = a.id;return true;
}
})
return val
}
}
}
<template>
<span>cellValue</span>
</template>

Calculating multiple totals from a v-for loop

I have a unique situation where I have a v-for loop of users (Staff) and inside that I have another v-for loop checking the leave a user has accumulated.
so to put it simply
v-for get user
//Print users name
v-for get any leave associated with this user
//Print that days Annual Leave
//Print that days Sick Leave
v-end
//Print total Annual Leave
//Print total Sick Leave
v-end
The leave database content has these values
Type: (Sick Leave, Annual Leave, Bereavement, etc)
Hours: integer
So essentially it will say
Thomas Annual Sick
------------------------------------------
Annual Leave 2 hours 0 Hours
Sick Leave 0 Hours 3 Hours
Annual Leave 4 Hours 0 Hours
-------------------------------------------
Total 6 Hours 3 Hours
John Annual Sick
------------------------------------------
Annual Leave 2 hours 0 Hours
Annual Leave 2 Hours 0 Hours
-------------------------------------------
Total 4 Hours 0 Hours
Now for the code and what I have so far:
<div v-for="user_list in user_list_filtered()">
<div class="user_heading"><h2>{{ user_list.first_name }}</h2></div>
<div class="report_content" v-for="userleave in user_leave_filtered(user_list['.key'])">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
//Get Leave
</div>
<div class="lsl_content">
//Get Sick Leave
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
// Print Total Leave
</div>
<div class="total_hours">
//Print Total Sick Leave
</div>
</div>
</div>
So if it is of Type Sick Leave add it to the second column and set the first column to 0 or if !== Sick Leave set first column to value and second column to 0. Then add each side up and print below.
I have tried some things as functions but I get infinite loops so I am kinda stuck as most posts are not as complicated as what I am trying to achieve.
Thanks for the help
Edit:
Additional functions
user_leave_filtered(userPassed) {
var self = this
return this.userLeave.filter(function (i) {
if (i.users_id === userPassed &&
((i.start_time >= self.getUnix(self.firstDate) && i.start_time <= self.getUnix(self.lastDate)) ||
(self.firstDate === null || self.firstDate === '' || self.lastDate === null || self.lastDate === ''))) {
return true
} else {
return false
}
})
},
user_list_filtered() {
var self = this
return this.userList.filter(function (i) {
var passed = false
if (self.userToShow === i['.key'] || self.userToShow === 'All') {
// Track whether to filter out this leave or not
self.userLeave.forEach(function (element) {
if (element.users_id === i['.key']) {
passed = true
} else {}
})
}
return passed
})
},
First rule of thumb, don't call functions in your HTML. Use computed properties instead.
You can get a filtered user list and map it to present the data you need per user.
Anyway, I recommend you to handle the mapping of "user leaves" in the backend, and bring the data as close as you'll use it in the client.
This is an example of how I'd address your case (notice I don't use the same object structure you are probably using, since you didn't provide the full code in your question)
new Vue({
el: "#app",
data: {
userList: [
{ id: 1, firstName: "Jon Doe" },
{ id: 2, firstName: "Jane Doe" }
],
userLeave: [
{ userId: 1, type: "anual", hours: 2 },
{ userId: 1, type: "sick", hours: 3 },
{ userId: 1, type: "anual", hours: 4 },
{ userId: 2, type: "anual", hours: 2 },
{ userId: 2, type: "sick", hours: 3 },
{ userId: 2, type: "anual", hours: 4 }
]
},
computed: {
usersById () {
if (!this.userList.length) return null;
// create a list of users by id and save a few iterations
return this.userList.reduce((list, user) => {
list[user.id] = user;
return list
}, {})
},
filteredUsers () {
if (!this.userLeave.length) return [];
const users = {}
this.userLeave.forEach(leave => {
const user = this.usersById[leave.userId]
if (user) {
if (leave.type === "sick") {
user.totalSick = typeof user.totalSick === "number"
? leave.hours + user.totalSick
: leave.hours;
} else {
user.totalAnual = typeof user.totalAnual === "number"
? leave.hours + user.totalAnual
: leave.hours;
}
if (user.leaves === undefined) user.leaves = []
user.leaves.push(leave)
users[user.id] = user
}
})
return users
}
}
})
.leave_type_content,
.days_taken_content,
.lsl_content,
.total_leave_title,
.total_hours,
.total_hours {
display: inline-block
}
<script src="https://unpkg.com/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<div v-for="user in filteredUsers"> <!-- NOTICE THE COMPUTED PROPERTY -->
<div class="user_heading"><h2>{{ user.firstName }}</h2></div>
<div class="report_content" v-for="userleave in user.leaves">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
{{ userleave.type === "anual" && userleave.hours || 0 }} hours
</div>
<div class="lsl_content">
{{ userleave.type === "sick" && userleave.hours || 0 }} hours
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
{{ user.totalAnual }}
</div>
<div class="total_hours">
{{ user.totalSick }}
</div>
</div>
</div>
</div>
I would create a computed property holding the users you want to display along with their mapped leave and totals. For example
computed: {
usersWithLeave () {
const unixFirstDate = this.firstDate && this.getUnix(this.firstDate)
const unixLastDate = this.lastDate && this.getUnix(this.lastDate)
// first map the leave entries by user for quick access
const leaveByUser = this.userLeave.reduce((map, element) => {
// Filter out by dates
if (
(!unixFirstDate || element.start_time >= unixFirstDate) &&
(!unixLastDate || element.start_time <= unixLastDate)
) {
const elements = map.get(element.users_id) || []
elements.push(element)
map.set(element.users_id, elements)
}
return map
}, new Map())
// now construct a filtered array of users then map to one with leave attached
return this.userList
.filter(({'.key': id}) => [id, 'All'].includes(this.userToShow) && leaveByUser.has(id))
.map(({'.key': id, first_name}) => {
const leave = leaveByUser.get(id)
return {
first_name,
leave, // an array of all leave elements
totals: leave.reduce((totals, element) => {
totals[element.type === 'Sick Leave' ? 'sick' : 'annual'] += element.Hours
return totals
}, { sick: 0, annual: 0 })
}
})
}
}
Whoo, that was more work than expected. This will produce an array of objects that look something like
{
first_name: 'Thomas',
leave: [
{ type: 'Annual Leave', Hours: 2 },
{ type: 'Sick Leave', Hours: 3 },
{ type: 'Annual Leave', Hours: 4 }
],
totals: {
sick: 3,
annual: 6
}
}
Now you can easily use this in your template
<div v-for="user in usersWithLeave">
<div class="user_heading"><h2>{{ user.first_name }}</h2></div>
<div class="report_content" v-for="userleave in user.leave">
<div class="leave_type_content">
{{ userleave.type }}
</div>
<div class="days_taken_content">
{{ userleave.type !== 'Sick Leave' && userleave.Hours || 0 }}
</div>
<div class="lsl_content">
{{ userleave.type === 'Sick Leave' && userleave.Hours || 0 }}
</div>
</div>
<div class="leave_content">
<div class="total_leave_title">
Total Leave Taken
</div>
<div class="total_hours">
{{ userleave.totals.annual }}
</div>
<div class="total_hours">
{{ userleave.totals.sick }}
</div>
</div>
</div>

v-if doesn't recognize value from array of object

I'm working on a vueJS file and have a component :
<div class="panel-heading" v-on:click="displayValue(feature.id)">
{{ feature.nom }}
</div>
<div class="panel-body" v-if="getDisplay(feature.id)">
foo
</div>
my function displayValue(id) :
displayValue(id){
for(let i =0; i<this.product_site.displayFeatures.length;i++){
if (this.product_site.displayFeatures[i].idFeature === id){
this.product_site.displayFeatures[i].show = !this.product_site.displayFeatures[i].show
}
}
console.log(this.product_site.displayFeatures)
},
First I am not very happy with that. I trie to do a .find but it didn't work :
this.product_site.displayFeatures.find(function(a){
a.idFeature === id
}).show = true
But I had an error telling me can not read 'show' of undefined
and my function getDisplay(id)
getDisplay(id){
this.product_site.displayFeatures.forEach(function(a){
if(a.idFeature === id){
return a.show
}
})
}
same as before if I try with a find.
Anyway, I thought it would work with that, but when I do console.log(this.product_sites.displayFeatures) I have the array with the modified value but foo is not shown