saldo counter in vue for loop gives infinite loop error - laravel

(There's a PD)
I have a page where I show account movements.
In the DB there is the date and the amount.
A separate table holds a saldo for beginning of each month.
Aditionally the table is filtrable by start and end date
The filtered entries from the bank account movements have a date and an amount and this is working fine.
I am trying now to fusion that with the account-saldo.
The start is easy: take and show startSaldo from DB
On render side I have a v-for loop going though each movement
strange is I am getting an infinite loop on an unexpected method
Rendering:
<tr v-for="movimiento in this.filteredEntries">
...
<td>{{calculateSaldo(movimiento.cobro_id,movimiento.monto)}}</td>
...
</tr>
saldo is a simple property (not a calculated one), at the moment initiated with 0 and only used within the method
and this is the method
calculateSaldo(cobro_id,monto){
//cobro_id and monto are
return 0; // <<<< without this I get the infinite loop error. But why?
if (cobro_id){
this.saldo=this.saldo+parseInt(monto);
} else {
this.saldo= this.saldo-parseInt(monto);
}
console.log(this.saldo);
// <<< a return 0 here produces also an infinite loop error
return this.saldo;
},
I have no clue where to futher look...
PD: After some more tests, apparently as soon as I reassign a value to this.saldo a re-render is initiated. How overcome that?
PPD: Apparently the re-render happens after reaching the last (by Date) filtered Entity

As described here when you reassign a value in state render triggered and it causes an infinite loop.
So I tried to recreate a basic version of your component and I think you can solve this on your filteredEntries computed property.
computed: {
filteredEntries() {
let lastSaldo = this.saldo;
return this.entries.map((item) => {
if (item.cobro_id) {
lastSaldo = parseInt(lastSaldo) + parseInt(item.monto);
} else {
lastSaldo = parseInt(lastSaldo) - parseInt(item.monto);
}
item.saldo = lastSaldo;
return item;
});
},
},
Complete code of my recreation of your component example:
<template>
<div>
<table>
<tr v-for="(movimiento, index) in this.filteredEntries" :key="index">
<td>{{ movimiento.saldo }}</td>
</tr>
</table>
</div>
</template>
<script>
export default {
data() {
return {
saldo: 0,
entries: [
{
cobro_id: null,
monto: 5,
},
{
cobro_id: 2,
monto: 10,
},
{
cobro_id: null,
monto: 5,
},
{
cobro_id: 1,
monto: 25,
},
],
};
},
computed: {
filteredEntries() {
let lastSaldo = this.saldo;
return this.entries.map((item) => {
if (item.cobro_id) {
lastSaldo = parseInt(lastSaldo) + parseInt(item.monto);
} else {
lastSaldo = parseInt(lastSaldo) - parseInt(item.monto);
}
item.saldo = lastSaldo;
return item;
});
},
},
};
</script>

Related

(Vue) Impact on performance of local scope variables in computed properties

Does defining variables inside of a computed property have any impact on the perfomance of Vue components?
Background: I built a table component which generates a HTML table generically from the passed data and has different filters per column, filter for the whole table, sort keys, etc., so I'm defining a lot of local variables inside the computed property.
Imagine having an array of objects:
let data = [
{ id: "y", a: 1, b: 2, c: 3 },
{ id: "z", a: 11, b: 22, c: 33 }
]
..which is used by a Vue component to display the data:
<template>
<div>
<input type="text" v-model="filterKey" />
</div>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filteredData" :key="item.id">
<td v-for="(value, key) in item" :key="key">
{{ value }}
</td>
</tr>
</tbody>
</table>
</template>
The data gets filtered via input:
<script>
export default {
props: {
passedData: Array,
},
data() {
return {
filterKey: null,
};
},
computed: {
filteredData() {
// defining local scope variables
let data = this.passedData;
let filterKey = this.filterKey;
data = data.filter((e) => {
// filter by filterKey or this.filterKey
});
return data;
},
},
};
</script>
My question refers to let data = .. and let filterKey = .. as filteredData() gets triggered from any change of the filterKey (defined in data()) so the local variable gets updated too, although they're not "reactive" in a Vue way.
Is there any impact on the performance when defining local variables inside a computed property? Should you use the reactive variables from data() (e. g. this.filterKey) directly inside of the computed property?
The best way to test if something affects performance, is to actually test it.
According to my tests below, it is consistency more than 1000% slower to use this.passedData instead of adding a variable on top of the function. (869ms vs 29ms)
Make sure you run your benchmarks on the target browsers you write your application for the best results.
function time(name, cb) {
var t0 = performance.now();
const res = cb();
if(res !== 20000000) {
throw new Error('wrong result: ' + res);
}
var t1 = performance.now();
document.write("Call to "+name+" took " + (t1 - t0) + " milliseconds.<br>")
}
function withoutLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += this.hi + this.hi2;
}
return sum;
},
}
})
return vue.test;
}
function withLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = this.hi;
const hi2 = this.hi2;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
function benchmark() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = 1;
const hi2 = 1;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
time('withoutLocalVar - init', withoutLocalVar);
time('withLocalVar - init', withLocalVar);
time('benchmark - init', benchmark);
time('withoutLocalVar - run1', withoutLocalVar);
time('withLocalVar - run1', withLocalVar);
time('benchmark - run1', benchmark);
time('withoutLocalVar - run2', withoutLocalVar);
time('withLocalVar - run2', withLocalVar);
time('benchmark - run2', benchmark);
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>

convert select to vue-select with dynamic data (Laravel & Vuejs)

I have dynamic products list to create an invoice. Now I want to search the product from select->option list. I found a possible solution like Vue-select in vuejs but I could not understand how to convert my existing code to get benefit from Vue-select. Would someone help me please, how should I write code in 'select' such that I can search product at a time from the list?
My existing code is -
<td>
<select id="orderproductId" ref="selectOrderProduct" class="form-control input-sm" #change="setOrderProducts($event)">
<option>Choose Product ...</option>
<option :value="product.id + '_' + product.product_name" v-for="product in invProducts">#{{ product.product_name }}</option>
</select>
</td>
And I want to convert it something like -
<v-select :options="options"></v-select>
So that, I can search products also if I have many products. And My script file is -
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#poOrder',
data: {
orderEntry: {
id: 1,
product_name: '',
quantity: 1,
price: 0,
total: 0,
},
orderDetail: [],
grandTotal: 0,
invProducts: [],
invProducts: [
#foreach ($productRecords as $invProduct)
{
id:{{ $invProduct['id'] }},
product_name:'{{ $invProduct['product_name'] }}',
},
#endforeach
],
},
methods: {
setOrderProducts: function(event) {
//alert('fired');
var self = this;
var valueArr = event.target.value.split('_');
var selectProductId = valueArr[0];
var selectProductName = valueArr[1];
self.orderEntry.id = selectProductId;
self.orderEntry.product_name = selectProductName;
$('#invQuantity').select();
},
addMoreOrderFields:function(orderEntry) {
var self = this;
if(orderEntry.product_name && orderEntry.quantity && orderEntry.price > 0) {
self.orderDetail.push({
id: orderEntry.id,
product_name: orderEntry.product_name,
quantity: orderEntry.quantity,
price: orderEntry.price,
total: orderEntry.total,
});
self.orderEntry = {
id: 1,
product_name:'',
productId: 0,
quantity: 1,
price: 0,
total: 0,
}
$('#orderproductId').focus();
self.calculateGrandTotal();
} else {
$('#warningModal').modal();
}
this.$refs.selectOrderProduct.focus();
},
removeOrderField:function(removeOrderDetail) {
var self = this;
var index = self.orderDetail.indexOf(removeOrderDetail);
self.orderDetail.splice(index, 1);
self.calculateGrandTotal();
},
calculateGrandTotal:function() {
var self = this;
self.grandTotal = 0;
self.totalPrice = 0;
self.totalQuantity = 0;
self.orderDetail.map(function(order){
self.totalQuantity += parseInt(order.quantity);
self.totalPrice += parseInt(order.price);
self.grandTotal += parseInt(order.total);
});
},
setTotalPrice:function(event){
var self = this;
//self.netTotalPrice();
self.netTotalPrice;
}
},
computed: {
netTotalPrice: function(){
var self = this;
var netTotalPriceValue = self.orderEntry.quantity * self.orderEntry.price;
var netTotalPriceInDecimal = netTotalPriceValue.toFixed(2);
self.orderEntry.total = netTotalPriceInDecimal;
return netTotalPriceInDecimal;
}
}
});
Assuming that invProducts is an array of product objects and each product object has a product_name property, try this snippet.
<v-select #input="selectChange()" :label="product_name" :options="invProducts" v-model="selectedProduct">
</v-select>
Create a new data property called selectedProduct and bind it to the vue-select component. So, whenever the selection in the vue-select changes, the value of selectedProduct also changes. In addition to this, #input event can be used to trigger a method in your component. You can get the selected product in that method and do further actions within that event listener.
methods: {
selectChange : function(){
console.log(this.selectedProduct);
//do futher processing
}

VueJS: how can i use two computed properties inside one v-for?

I have this computed property:
computed: {
filteredCars: function() {
var self = this
return self.carros.filter(function(carro) {
return carro.nome.indexOf(self.busca) !== -1
})
},
},
and i'm using v-for like this:
<tr v-for="carro in filteredCars">
<td>{{carro.nome}}</td>
<td>{{carro.marca}}</td>
<td>{{carro.categoria}}</td>
<td>{{carro.motor}}</td>
<td>{{carro.cambio}}</td>
<td>{{carro.preco}}</td>
</tr>
but I need to create another computed property to limit my data quantity, how i call it inside the same v-for?
I'm trying to use filteredCars + another filter, in this case something like 'limit' filter from vue 1.x. I've done an example using Vue 1.x but i need to do using Vue 2.x.
Vue.filter('limit', function (value, amount) {
return value.filter(function(val, index, arr){
return index < amount;
});
<tr v-for="carro in carros | limit upperLimit>
...
</tr>
Just use Array.prototype.slice (Array.prototype.splice should work too) in the computed property.
data: {
carros: [...],
upperLimit: 30
},
computed: {
filteredCars: function() {
const arr = this.carros.filter(function(carro) {
return carro.nome.indexOf(self.busca) !== -1
});
if (arr.length > this.upperLimit) {
return arr.slice(0, this.upperLimit + 1);
}
return arr;
},
}

dropdown list with an extend option

I'm using the DropdownList component from react-widget. In my code, there are a couple of dropdowns that get their values from an Ajax call. Some of them, like a list of languages, are too big and it's very slow to get the list from Ajax and render it (takes 4 to 5 seconds!). I would like to provide to the dropdwon a small list of languages and an 'Extend' or 'Load Full List' option; if clicking on Extend the dropdown would be refreshed with the full list of languages.
Here is my solution: the code of the parent component:
const languages = ajaxCalls.getLanguages();
const langs = {"languages": [["eng", "English"], ["swe", "Swedish"], ["deu", "German"], ["...", "Load Full List"]]};
const common_langs = langs.languages.map(([id, name]) => ({id, name}));
<SelectBig data={common_langs} extend={languages} onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} />;
And here is the code for SelectBig component:
import React from 'react/lib/ReactWithAddons';
import { DropdownList } from 'react-widgets';
const PT = React.PropTypes;
export const SelectBig = React.createClass({
propTypes: {
data: PT.array,
value: PT.string,
onSelect: PT.func.isRequired,
},
maxResults: 50,
shouldComponentUpdate(nextProps, nextState) {
console.log("nextProps = " , nextProps, " , nextState = ", nextState);
const len = x => (x && x.length !== undefined) ? x.length : 0;
// fast check, not exact, but should work for our use case
return nextProps.value !== this.props.value
|| len(nextProps.data) !== len(this.props.data);
},
getInitialState(){
return {
lastSearch: '',
results: 0,
dataList: [],
};
},
select(val) {
if(val.id === "..."){
this.setState({dataList: this.props.extend})
}
this.props.onSelect(val.id);
},
filter(item, search) { .... },
renderField(item) { .... },
render() {
const busy = !this.props.data;
let data;
if(!this.props.extend){
data = this.props.data || [];
} else {
data = this.state.dataList;
}
return (
<DropdownList
data={data}
valueField='id'
textField={this.renderField}
value={this.props.value}
onChange={this.select}
filter={this.filter}
caseSensitive={false}
minLength={2}
busy={busy} />
);
}
});
But it doesn't look good: When the user chooses 'Load Full List', the dropdown list will be closed and user need to click again to see the updated list. Does anyone have a better solution or a suggestion to improve my code?
The picture shows how it looks like right now!
https://www.npmjs.com/package/react-select-2
Better go with this link, it will work

Sorting columns using asc/desc and default ordering

I have multiple columns in my table, for example:
id | name | amount | description
And I want to sort each column - on the first click in ascending order, on the second on descending order, on the third go back to default, and all over again.
The default is the id column sorded in asc order.
So, the default state in the reducer is:
sort: {
key: 'id',
desc: false
}
The next steps on clicking name column would be:
sort: {
key: 'name',
desc: false
}
sort: {
key: 'name',
desc: true
}
sort: {
key: 'id',
desc: false
}
The view calls an action using column's name as a parameter:
<td onClick={() => this.props.sort('name')}>Name</td>
<td onClick={() => this.props.sort('amount')}>Amount</td>
An action should dispatch such key and desc values so that it matches my pattern:
export function sort(key) {
return dispatch => {
};
};
How can I do this?
Here you go, brief explanation in code sample.
I setup 2 columns only, cause I am lazy, sorry.
Fiddle: https://jsfiddle.net/u1wru0gb/1/
const data = [
{ id: 1, name: 'Bruce' },
{ id: 3, name: 'Martin' },
{ id: 2, name: 'Andrew' },
];
/**
* Nothing interesting, just render...
*/
function Table({ data, sortByKey }) {
const renderRow = ({ id, name }, idx) => (
<tr key={idx}>
<td>{id}</td>
<td>{name}</td>
</tr>
)
return (
<table>
<tr>
<th onClick={sortByKey('id')}>ID</th>
<th onClick={sortByKey('name')}>Name</th>
</tr>
{ data.map(renderRow) }
</table>
);
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
sort: {
key: undefined,
// 0 - not ordering
// 1 - asc
// 2 - desc
order: 0,
},
};
this.sortByKey = this.sortByKey.bind(this);
}
sortedData() {
const { key, order } = this.state.sort;
// Only sort if key is provided & order != 0.
if (key && order) {
// Comparison function for "asc" sorting.
function compare(a, b) {
if (a[key] < b[key]) return -1;
if (a[key] > b[key]) return 1;
return 0;
}
// Attention! Sort mutates array, clone first.
return [...this.props.data].sort((a, b) => {
// Interesting part. Sort in "asc" order. Flip if want "desc" order!
return compare(a, b) * (order === 1 ? 1 : -1);
});
}
// Return original data (order = 0)
return this.props.data;
}
sortByKey(key) {
return () => {
const sort = (this.state.sort.key === key)
// Key matches, update order
? { key, order: (this.state.sort.order + 1) % 3 }
// Key differs, start with "asc" order
: { key, order: 1 };
this.setState({ sort });
}
}
render() {
return (
<Table data={this.sortedData()} sortByKey={this.sortByKey} />
);
}
}
ReactDOM.render(
<Container data={data} />,
document.getElementById('container')
);

Resources