how add element in vuex and dont start computed value? - vue.js

I try to add fractions on vue/vyex all addition happened in getter so its means after we change some fields, we start computed value.
And when i click on button "add new fractions", i create new object with computed property and taking error in getter, because how i understand, computed check changes and getter start work, but there don't have new property and i take that error : Error in v-on handler: "TypeError: Cannot read property 'denominator' of undefined"
store
export default new Vuex.Store({
state: {
fractions: [
{
id: 1,
numerator: 0,
denominator: 0,
},
{
id: 2,
numerator: 0,
denominator: 0,
},
],
},
actions,
mutations: {
changeInput(state, payload) {
state.fractions.forEach((el) => {
if (el.id === payload.id) {
el[payload.key] = payload[payload.key];
}
});
},
addFraction(state) {
state.fractions.push({
id: state.fractions.length + 1,
numerator: 0,
denominator: 0,
});
},
deleteFraction(state, id) {
state.fractions.forEach((el, i) => {
if (el.id === id) {
state.fractions.splice(i, 1);
}
});
},
},
getters: {
takeSum(state) {
const sum = {
denominator: 0,
numerator: 0,
};
state.fractions.reduce((prev, curr) => {
sum.denominator = prev.denominator + curr.denominator;
sum.numerator = prev.numerator + curr.numerator;
});
return sum;
},
},
});
component computed property
computed: {
...mapState([
'fractions',
]),
...mapGetters([
'takeSum',
]),
},
and template
<Fraction
v-for="(fraction, index) in fractions"
:key="index"
:numerator=fraction.numerator
:denominator=fraction.denominator
:id="fraction.id"
#changeFractionInput=changeFractionInput
v-bind:onClick="deleteFraction"
/>
<div>sum: {{takeSum}}</div>

Your problem is in takeSum. You are not using the reducer correctly.
A reducer walks over every element of an array, and computes a single value from it. This value can be anything, including an object. You must however return the result between every item. What you return is used in the next cycle. Your code would turn into something like this:
const sum = state.fractions.reduce((prev, curr) => {
const newSum = { ...prev };
newSum.denominator = prev.denominator + curr.denominator;
newSum.numerator = prev.numerator + curr.numerator;
return newSum;
});
Because you did not return anything, the first time the reducer (the function inside reduce(..) is called, it is called with the first and second element of your array. The second time it is called with prev being undefined (you did not return anything), and curr being the third element in your array.
I must however say that you are not correctly calculating the sum of fractions. If we take the sum of 1/2 and 1/3, you would say that the sum is in fact 2/5. However, a quick calculation shows us that this is not the case.
If you want to sum two fractions, you must make sure that the denominators of both are equal. For our previous example, this would be 1/2 = 3/6 and 1/3 = 2/6, so the sum would be 5/6. A typical sum of a/b + c/d would be ((a*d) + (b*c)) / (b*d).

Related

how does the index argument work in the reduce.method in Javascript

I'm trying to understand the reduce method but I got stuck over something. If I write:
const myArr = [1, 2, 3, 4, 7, 5];
const sum = myArr.reduce((acc,el)=>{
return acc+el
});
console.log(`The sum is ${sum}`);
I get the correct output of 22 which is my sum.
But if I write this:
const items = [{
name: "Bike",
price: 100
},
{
name: "TV",
price: 200
},
{
name: "Album",
price: 10
},
{
name: "Book",
price: 5
},
{
name: "Phone",
price: 500
},
{
name: "Computer",
price: 1000
}
]
const total = items.reduce((acc, el) => {
return acc + el.price
});
console.log(total);
I don't get the total unless I initialise the index to 0. I don't understand why in the first case it works but not in the second.
Thanks for any help!!
The .reduce functions seems to initially assigns "acc" the type of the first element of what is being looped through, before it knows what you will be doing with it (could be math or string manipulation). You are trying to do math.
In the first case that type is a number, so all is well, but in the second, it is an object. Thus in the second case, you are adding a number (el.price) to an object ({name: "Bike", price: 100}), thus converting it to a string. From here on out, you will just be concatenating strings rather then doing math.
By setting acc to 0, ur reassigning it a type of number, and thus it works.
So I think it is good practise to always assign acc in the reduce function to avoid these types of error.
The return of the reducer function (in this case const reducer =...) is assigned to the accumulator, and it is "remembered" across each iteration so if you try this way:
const reducer = (accumulator, currentValue) => ({
price: accumulator.price + currentValue.price
});
items.reduce(reducer);
// console.log outputs { price: 1815 }
Because you are handling objects, you need to return an object because if you return just (acc, cur) => acc.price + cur.price you get a NaN error because a number is not compatible with an object (and the return of the reducer function is assigned to the accumulator).
Check these console logs, I believe they will help understand what is going on with the reducer:
Note that the first time the reducer runs the accumulator is the index 0 of the array.
Please let me know if you have any other questions in the comments below.

Vuejs track input field

I need to check whether my input field is empty or not.
Logic
if form.name has value, use increase function
if form.name is empty, use decrease function
do not use increase, decrease functions on each character that user inputs or removes
Code
<el-form-item label="Product name *">
<el-input v-model="form.name"></el-input>
</el-form-item>
methods: {
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
any idea?
Update
Script
data() {
return {
form: {
name: '', // require
slug: '',
price: '', // require
supplier_id: '', // require
new_price: '',
base_price: '',
sku: '',
qty: 1, // require
active: '', // require
photo: '',
photos: [],
shortDesc: '',
longDesc: '',
origin: '',
tags: [],
brand_id: '', // require
categories: [],
user_id: '',
seoTitle: '',
seoTags: '',
seoPhoto: '',
seoDescription: '',
variations: [],
options: [],
condition: '', // require
isbn: '',
ean: '',
upc: '',
height: '',
weight: '',
lenght: '',
width: '', // require
},
}
},
methods: {
onSubmit(e) {
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
// do my things
})
.catch(function (error) {
console.log('error', error);
});
},
}
HTML
<el-form ref="form" :model="form" label-width="120px" enctype="multipart/form-data">
// my inputs (listed in form part in script above)
<el-button type="primary" #click="onSubmit" native-type="submit">Create</el-button>
</el-form>
One possible solution would be to use #focus and #blur events to check if form.name has a value before increasing or decreasing, this would be fired on focus or on blur events, so you will not have the methods fired on each character input or deletion.
for example:
<el-form-item label="Product name *">
<el-input #focus="checkName" #blur="checkName" v-model="form.name"></el-input>
</el-form-item>
methods: {
checkName() {
//If form.name has a value then run increase method, otherwise run decrease method
!!this.form.name ? this.increase() : this.decrease()
},
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
You can see a working fiddle HERE
UPDATE
Alright so i did follow the rules you state on your question, and i didn't know you wanted to get the percentage of completion of the form, so in order to do that, i would suggest to use a computed property, you can read more about computed properties in the VueJS Documentation, this way the percentage is calculated based on the criteria we can give it, and only if the form has values.
computed: {
formProgress: function () {
let totalInputs = Object.keys(this.form).length;
let filledInputs = 0;
Object.values(this.form).forEach(val => {
if (!!val){
filledInputs++;
}
});
return (filledInputs/totalInputs)*100
}
},
As you can see in one single computed property you can handle the complex logic and return the value reactively, to explain it better, i'm counting the lenght of the form object, to get total number of inputs in your form, so it's important to have all your form data inside the form data object, then i convert that object to an array to iterate it, and i check if each property has a value on it, if does it, i add 1 to the filledInputs counter, and finally just return a simple math to get the percentage. please check the new Fiddle here to see it in action:
FORM PROGRESS FIDDLE
If you have any other doubt just let me know.
UPDATE 2:
All right in order to only count for specific inputs for the form progress, i have modified the code to work based on an array that contains the names of the properties that are required. here is the full code:
data() {
return {
form: {
name: '',
lastName: null,
categories: [{}],
},
requiredFields: ['name', 'categories']
};
},
computed: {
formProgress: function () {
let totalInputs = this.requiredFields.length;
let filledInputs = 0;
Object.entries(this.form).forEach(entry => {
const [key, val] = entry;
if (this.requiredFields.includes(key)){
switch (val.constructor.name) {
case "Array":
if (val.length !== 0){
if (Object.keys(val[0]).length !== 0){
filledInputs++;
}
}
break
case "Object":
if (Object.keys(val).length !== 0){
filledInputs++;
}
break
default:
if (!!val){
filledInputs++;
}
}
}
});
return Number((filledInputs/totalInputs)*100).toFixed(1)
}
},
And here is the updated FIDDLE
As you can see now i'm using Object.entries to get the key and value of the form object, so you can have a single form object to send to your backend, this way i'm checking first if the key is in the required fields, and if has a value, so all you need to do is update the requiredFields data array with the same names as your inputs data property to make the validation work, also there is a validation depending if is array, array of objects, or object, that way it will validate input on each data type.
Hope this works for you.

How detect which data of the component has been updated at Vue Lifecycle?

There are a lot of data and computed params and methods in a Vue component sometimes, all of them are interconnected. And when I change one parameter, then a few more parameters update with it (data, computed params, especcialy when some parama are changing in the Vuex actions or mutations and it difficult to track them all). And I can't get which ones exactly...
Is it possible to get all parameters which were updated in the updated()?
Please read comments in the simple example below:
<script>
export default {
/**
* This is Basket.vue component
* The basket has some fruits.
*/
data () {
return {
apples: 5,
oranges: 5,
bananas: 5
}
},
/**
* Someone randomly taking one fruit when will see the basket
*/
mounted () {
let randomFruit = Object.keys(this.$data).sort(() => Math.random() - 0.5); // ["oranges", "bananas", "apple"]
this.takeFruit(randomFruit[0]);
},
methods : {
takeFruit(fruit) {
this[fruit]--;
}
},
computed : {
any_apples () {
return this.apples > 0
},
any_oranges () {
return this.oranges > 0
},
any_bananas () {
return this.bananas > 0
},
total () {
return this.apples + this.oranges + this.bananas
}
},
/**
* And there is a question here about updated Vue lifecycle.
* How detect which data of the component exactly has been updated?
* For example, we took one apple, updated params are "apples", "any_apples", "total", how can we get it here?
*/
updated () {
let updated_params = ... ; // how get ["apples", "any_apples", "total"]?
console.log("What exactly updated?", updated_params);
},
}
</script>

Vue v-for list not re-rendering after computed data update

I am implementing pagination for a huge list of cards, I display 10 cards at once and wish to show the 10 next (or 10 previous) by clicking on two buttons.
Here's how I do it:
export default {
...
data() {
return {
pois: [], // My list of elements
pageNumber: 0, // Current page number
};
},
props: {
size: {
type: Number,
required: false,
default: 10, // 10 cards per page
},
},
computed: {
pageCount() {
// Counts the number of pages total
const l = this.pois.length;
const s = this.size;
return Math.floor(l / s);
},
paginatedData() {
// Returns the right cards based on the current page
const start = this.pageNumber * this.size;
const end = start + this.size;
return this.pois.slice(start, end);
},
},
methods: {
nextPage() {
this.pageNumber += 1;
},
prevPage() {
this.pageNumber -= 1;
},
}
...
};
And my template:
<div v-for="poi in paginatedData" :key="poi.id">
<card :poi="poi"/>
</div>
Everything should work (and a page change does output the correct cards in the console) but my list is not updated even though the computed method is called on each click.
What is causing this issue? I've read it could be linked to a :key value missing, but it's there, and no data is being updated directly and manually in the array, only sliced out.
First, try this change, just for sure, and let me know in comment it works or not.
export default {
...
computed: {
paginatedData() {
...
const end = start + this.size - 1;
...
},
...
};
And yes: instead of id, try to use index:
<div v-for="(poi, idx) in paginatedData" :key="idx">
<card :poi="poi"/>
</div>

Vuejs2 - computed property in components

I have a component to display names. I need to calculate number of letters for each name.
I added nameLength as computed property but vuejs doesn't determine this property in loop.
var listing = Vue.extend({
template: '#users-template',
data: function () {
return {
query: '',
list: [],
user: '',
}
},
computed: {
computedList: function () {
var vm = this;
return this.list.filter(function (item) {
return item.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
},
nameLength: function () {
return this.length; //calculate length of current item
}
},
created: function () {
this.loadItems();
},
methods: {
loadItems: function () {
this.list = ['mike','arnold','tony']
},
}
});
http://jsfiddle.net/apokjqxx/22/
So result expected
mike-4
arnold-6
tony-4
it seems there is some misunderstanding about computed property.
I have created fork from you fiddle, it will work as you needed.
http://jsfiddle.net/6vhjq11v/5/
nameLength: function () {
return this.length; //calculate length of current item
}
in comment it shows that "calculate length of current item"
but js cant get the concept of current item
this.length
this will execute length on Vue component it self not on that value.
computed property work on other property of instance and return value.
but here you are not specifying anything to it and used this so it wont able to use any property.
if you need any more info please comment.