So the problem is occurring when you’re trying to reduce an empty array - if this happens:
Array.from(table.rows).slice(2) === [{}]
… as it will, when there are no row
elements in the table, or if the row[4].innerHTML
doesn’t contain a valid number, then parseFloat(row.cells[4].innerHTML)
is going to either error, or give you back a NaN
value.
Easiest workaround? A combination of two things: optional chaining and the logical OR
.
How might those help? Consider the following object:
const person = {
name: 'Billy Jo Roberts',
age: 39,
pets: {
dog: {name: 'Spike'}',
cat: [
{ name: "Fluffy"},
{ name: "Sharptooth Cookie-dough"}
]
}
}
// this line will give us an error, as there is no 'name' property on dog
console.log(person.pets.iguana.name);
// Uncaught TypeError: person.pets.iguana is undefined
// this won't error, but *will* give us a usable value:
console.log(person.pets.iguana?.name);
// undefined
Optional chaining is the question mark in there, right after iguana
. If we are going through that chain and we encounter an undefined property (the one that would have given us the error above), the question mark short-circuits the chaining and returns undefined
. Gets rid of the error, but parseFloat(undefined)
is still going to show up as NaN
(Not a Number).
That’s where the Logical OR comes in handy. If we did this:
console.log( parseFloat(undefined) || 42 );
The ||
forces an evaluation, and will return the first “truthy” value. In this case, the parseFloat(undefined)
evaluates as NaN
. That NaN
is one of those things that javascript looks at and says “Is this something a Boolean would coerce to true
, or false
?” (that’s the truthy or falsy bit). NaN
is coercible to false
, so the ||
proceeds to the next thing to be evaluated. In this case, 42
. Now any number but zero will coerce to true, so we’ve found our “truthy” value, and that is the one that we’ll see, rather than that NaN
.
But how does that help you? Welp, let’s look closer. In here:
Array.from(table.rows).slice(1).reduce((total, row) => {
console.log(total);
return parseInt(total) + /* Either the row[4] if its available and a valid number, or 0 */;
}, 0);
So we want to find a way, inline, to code that comment. We could code it longer-hand, like this:
Array.from(table.rows).slice(1).reduce((total, row) => {
// this will be either 0, or the value from the `Subtotal` column.
let rowSubtotal = 0;
// do we have a valid cells[4]? does it actually contain a number?
if(row.length > 5 && !Number.isNaN(parseFloat(row.cells[4].innerHTML) ){
// here, the row has at least five columns, and the
// value we got from innerHTML is a valid number, so
rowSubtotal = parseFloat(row.cells[4].innerHTML)
}
console.log(total);
return parseInt(total) + rowSubtotal;
}, 0);
With that, we check if we have a fifth column in the row, and if the value in that fifth column is a valid number. And that’s a lot of code. The ES6 shorthand way of doing the same thing?
let grandTotal = Array.from(table.rows).slice(1).reduce((total, row) => {
console.log(total);
// look closely at the last half of the line below.
return parseInt(total) + (parseFloat(row.cells[4]?.innerHTML) || 0);
}, 0);
In that last one, the parseFloat(row.cells[4]?.innerHTML)
will either return a numeric value, or an NaN
- but it won’t give an error anymore. The optional chaining got rid of the error.
And the (parseFloat(row.cells[4]?.innerHTML) || 0)
says “If the first part of this coerces to Boolean true
, we’ll use that. If not, let’s just use zero.”
So that line does the same thing as the multi-line evaluation, without having to check that we have a row, and that the row has five columns, and that the fifth column contains a valid number - does it in one swell foop.
Now, the other issue you might be having - move the call to updateGrandTotal()
in your click handler to the end of the function, after you’ve added the row, and your total will accurately reflect the addition.