Why does it return NaN?

Hello. I’m trying to collect the subtotals from my table to get the grand total each time the “SCAN” button is clicked or a row is removed.

The code:

I cannot figure out why my results are ending up in a NaN value on this row:

Array.from(table.rows).slice(1).reduce((total, row) => {

If I slice it at 2, like below, then I don’t get a NaN value but instead, the Grand Total skips the first subtotal.

Array.from(table.rows).slice(2).reduce((total, row) => {

What is the issue with my code?

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. :wink:

1 Like

Snowmonkey!! It’s been years :joy: :star_struck:

The explanation and elaborate breakdown you provided is so informative, that’s a whole day’s lesson up there :ghost: :sob: :rofl:

The edit works flawlessly with the logical operator you pinpointed. I want to go through the other solution while i’m on a learning curve figuring out how you came to pinpoint this problem. Still struggle a lot with debugging ! :sleepy:

1 Like

I was wondering if you’d remember me. :grin:

So when I opened your code and ran it, the first thing I noticed was that if I clicked the button and added a row, the total was always a row behind - when there was one row with a subtotal of 22, the total showed 0. When there were two rows, each with a subtotal of 22, then the total showed 22 rather than 44. So I knew you were calculating the total before you were adding the row. That was the easy bit.

Then, I went into your updateGrandTotal function, and did a console.log(JSON.stringify(row) ) inside the reduce block - I wanted to see what that was, each time through the loop. And when there were no rows, in the row, I was getting {} - that tells me that there was an issue calculating no rows.

And I’m trying to get more comfortable with that optional chaining thing, it’s very new. On the plus side it looks neat. On the minus side, I’m not sure how broad support is for it yet.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.