Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Rod2rick's avatar

Sum number in vuejs

I want to sum a total of my cart, but the result return NaN here is my code

cartTotalAmount() {
      let total = 0;
      for (let item in this.cart) {
        total = total + (this.cart[item].quantity * this.cart[item].price)
      }
      return total;
    },

and

 <h2>{{ cartTotalAmount }} €</h2>

Thank you for your help.

0 likes
19 replies
MarianoMoreyra's avatar

Hi @rod2rick

It's difficult without seeing what you cart or item looks like, although inferring from what you've posted, you may try with something like this:

cartTotalAmount() {
      let total = 0;
      
      total = this.cart.reduce( (acc, item) => {
          return acc + (item.quantity * item.price)
      }, 0)

      return total;
    },

or if you want to simplify it a bit:

cartTotalAmount() {
    return this.cart.reduce( (acc, item) => {
        return acc + (item.quantity * item.price)
    }, 0)
}

Hope this works for you!

EDITED: I've added a second parameter to the reduce method in order to establish 0 as the initial value for the accumulator.

Rod2rick's avatar

@marianomoreyra This

cartTotalAmount() {
      let total = 0;
      
      total = this.cart.reduce( (acc, item) => {
          return acc + (item.quantity * item.price)
      }, 0)

      return total;
    },

returns 0 with default value 0 in the vue console, and this one

cartTotalAmount() {
    return this.cart.reduce( (acc, item) => {
        return acc + (item.quantity * item.price)
    }, 0)
}

returns exactly NaN.

MarianoMoreyra's avatar

@rod2rick please share what do you get if you do

console.log(this.cart) 

Just before the return in the second case

MarianoMoreyra's avatar

@rod2rick so that's your problem...you don't seem to have a cart to sum, at least not available to this component.

Please share the rest of the javascript code of your component if possible

Rod2rick's avatar

This is the entire page

<template>
  <div class="home-container">
    <h1>Articles</h1>

    <!-- search display -->
    <input
      v-model="searchKey"
      id="search"
      type="search"
      placeholder="Rechercher..."
      autocomplete="off"
    />
    <span v-if="searchKey && filteredList.length >= 1">
      {{ filteredList.length }} resultat
      <span v-if="filteredList.length >= 2">s</span>
    </span>

    <!-- cards display -->
    <div class="card-cart-container">
      <div class="card-container">
        <div
          v-for="produit in filteredList"
          class="card"
          v-bind:key="produit.id"
        >
          <div class="img-container">
            <img :src="'/uploads/produits/' + produit.image" />
          </div>

          <div class="card-text">
            <h3>{{ produit.nom }}</h3>
            <span>{{ produit.prix }} FCFA</span>
          </div>

          <div class="card-icons">
            <div class="like-container">
              <input
                type="checkbox"
                :value="produit.id"
                name="checkbox"
                v-bind:id="produit.id"
                v-model="liked"
                @click="setLikeCookie()"
              />
              <label v-bind:for="produit.id">
                <i class="fas fa-heart"></i>
              </label>
            </div>

            <div class="add-to-cart">
              <button v-on:click="addToCart(produit)">
                <i class="fas fa-plus-square"></i>
              </button>
            </div>
          </div>
        </div>

        <!-- no result message -->
        <div v-if="filteredList.length == []" class="no-result">
          <h3>Désolé</h3>
          <p>Aucun résultat trouvé</p>
        </div>
      </div>
      <!-- {{liked}} -->

      <!-- cart display -->
      <transition name="cart-anim">
        <div v-if="cart.length > 0" class="shopping-cart" id="shopping-cart">
          <h2>Commandes</h2>

          <transition-group name="item-anim" tag="div" class="item-group">
            <div
              v-for="(produit, id) in cart"
              class="item"
              v-bind:key="produit.id"
            >
              <div class="img-container">
                <img :src="'/uploads/produits/' + produit.image" />
              </div>

              <div class="item-description">
                <h4>{{ produit.nom }}</h4>
                <p>{{ produit.prix }} FCFA</p>
              </div>

              <div class="item-quantity">
                <h6>quantité : {{ produit.quantity }}</h6>

                <div class="cart-icons">
                  <button v-on:click="cartPlusOne(produit)">
                    <i class="fa fa-plus"></i>
                  </button>
                  <button v-on:click="cartMinusOne(produit, id)">
                    <i class="fa fa-minus"></i>
                  </button>
                  <button @click="cartRemoveItem(id)">
                    <i class="fa fa-trash"></i>
                  </button>
                </div>
              </div>
            </div>
          </transition-group>

          <div class="grand-total">
            <div class="total">
              <h2>Total</h2>
              <h2>{{ cartTotalAmount }} FCFA</h2>
            </div>
            <h6>Total articles : {{ itemTotalAmount }}</h6>
          </div>
          <div class="order-button">
            <button>Commander</button>
          </div>
        </div>
      </transition>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      produits: [],
      searchKey: "",
      liked: [],
      cart: [],
    };
  },
  computed: {
    filteredList() {
      return this.produits.filter((produit) => {
        return produit.nom.toLowerCase().includes(this.searchKey.toLowerCase());
      });
    },
    getLikeCookie() {
      let cookiValue = JSON.parse($cookies.get("like"));
      cookieValue == null ? (this.liked = []) : (this.liked = cookiValue);
    },
cartTotalAmount() {
    return this.cart.reduce( (acc, item) => {
        return acc + (item.quantity * item.prix)
    }, 0)
}
  },
  methods: {
    loadProduits: function () {
      axios
        .get("/api/produits")
        .then((response) => {
          this.produits = response.data.data;
          this.loading = false;
        })
        .catch(function (error) {
          console.log(error);
        });
    },
    addToCart(produit) {
      // check if already in array
      for (let i = 0; i < this.cart.length; i++) {
        if (this.cart[i].id === produit.id) {
          return this.cart[i].quantity++;
        }
      }
      this.cart.push({
        id: produit.id,
        image: produit.image,
        nom: produit.nom,
        prix: produit.prix,
        quantity: 1,
      });
    },
    cartPlusOne(produit) {
      produit.quantity = produit.quantity + 1;
    },
    cartMinusOne(produit, id) {
      if (produit.quantity == 1) {
        this.cartRemoveItem(id);
      } else {
        produit.quantity = produit.quantity - 1;
      }
    },
    cartRemoveItem(id) {
      this.$delete(this.cart, id); 
    },
  },
  setLikeCookie() {
    document.addEventListener("input", () => {
      setTimeout(() => {
        $cookies.set("like", JSON.stringify(this.liked));
      }, 300);
    });
  },
  mounted() {
    this.loadProduits();
    this.getLikeCookie();
  },
};
</script>

MarianoMoreyra's avatar

@rod2rick in that codepen, I've tested it both with Vue 2 and Vue 3 and works for both (despite some warnings and errors in console)

MarianoMoreyra's avatar

Well @rod2rick I don't know what other difference could you have in your implementation that makes it fail.

Have you checked out the codepen I've sent you? It works as you expect or am I missing something?

Rod2rick's avatar

@marianomoreyra Yes i have checked it, may be it is the template im using, i don't know. Im using coreui as template.

MarianoMoreyra's avatar

Unless your axios is returning a different object structure inside produits @rod2rick

Please, make a console.log(this.produits) at the end of the .then() method of your axios call and share here the result:

      axios
        .get("/api/produits")
        .then((response) => {
        this.produits = response.data.data;
        this.loading = false;
        console.log(this.produits);
      })
rodrigo.pedra's avatar

If produit.prix inside the cart is not a number, or cannot be coerced to a number, then item.quantity * item.prix will result in NaN.

For example a price with a comma as decimal separator cannot be coerced to a number.

Can you confirm if the products' prices are numbers or number-like strings?

Also they should not contain monetary symbols or thousands separators before using in the multiplication.

Please add this before the last closing </div>, add some items to the card and show us the results if you can:

    <pre>{{ cart }}</pre>
  </div>
</template>
1 like
MarianoMoreyra's avatar

Good catch there @rodrigo.pedra !!

I didn't even think about numbers formatting...I've just assumed they should be coming as expected.

1 like
rodrigo.pedra's avatar
Level 56

Thanks @marianomoreyra !

I remember this kind of stuff from previous pain points that once costed me a lot of time.

As we use comma as decimal separators in Brazil, I had to deal a lot with this kind of oddities.

Let's hope it is something like this, if so it is easier to fix.

MarianoMoreyra's avatar

Yes @rodrigo.pedra

I've dealt with those same issues several times! But in this case, as the cart wasn't showing anything on a console.log (according to OP) I assumed that the error was somewhere else.

That's why I've created the codepen, to have a better chance to find the problem.

I should have started by asking to dump to console the products fetched with axios as a first step!

1 like
Rod2rick's avatar

That is it i have formated the price in my ProductResource instead of

public function toArray($request)
    {
        return [
            'id' => $this->id,
            'nom' => $this->nom,
            'description' => $this->description,
            'prix' => number_format( $this->prix, 0, '.', ' ' ),
            'type' => $this->type,
            'image' => $this->image
        ];
    }

it is

public function toArray($request)
    {
        return [
            'id' => $this->id,
            'nom' => $this->nom,
            'description' => $this->description,
            'prix' => $this->prix,
            'type' => $this->type,
            'image' => $this->image
        ];
    }

and it works properly. Thank you

@marianomoreyra , @rodrigo.pedra By the way how can i format the number properly please.

1 like
rodrigo.pedra's avatar

Step 1

Create a file called filters.js.

This is from one of my projects with locale changed to fr and currency changed to CFA (FCFA didn't work when I tested).

Note you can use another French based locale, such as fr-CF for a Central African Republic French locale. As I don't know where you, or your project's client, is from, I choose to use just fr.

import Vue from 'vue';

const integerFormatter = new Intl.NumberFormat('fr', {
    maximumFractionDigits: 0,
});

const decimalFormatter = new Intl.NumberFormat('fr', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
});

const percentageFormatter = new Intl.NumberFormat('fr', {
    style: 'percent',
    minimumFractionDigits: 1,
    maximumFractionDigits: 1,
});

const currencyFormatter = new Intl.NumberFormat('fr', {
    style: 'currency',
    currency: 'CFA',
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
});


Vue.filter('floor', function floorFilter(value) {
    const number = Number(value);

    return Number.isFinite(number) ? Math.floor(number) : null;
});

Vue.filter('integer', function integerFilter(value) {
    const number = Number(value);

    if (!Number.isFinite(number)) {
        return null;
    }

    return integerFormatter.format(number);
});

Vue.filter('percentage', function percentageFilter(value) {
    const number = Number(value);

    if (!Number.isFinite(number)) {
        return null;
    }

    return percentageFormatter.format(number);
});

Vue.filter('decimal', function decimalFilter(value) {
    const number = Number(value);

    if (!Number.isFinite(number)) {
        return null;
    }

    return decimalFormatter.format(number);
});

Vue.filter('currency', function currencyFilter(value) {
    const number = Number(value);

    if (!Number.isFinite(number)) {
        return null;
    }

    return currencyFormatter.format(number);
});

This will register 5 new global filters within Vue: floor, integer, percentage, decimal, and currency.

Note I used some defaults based on the project I got this snippet from. If your locale uses 3 decimal places (some places do), or any other value, you can customize those options.

Thousands separators will be added according to the chosen locale.

Step 2

But for them to be available on your components you need to import this file in your entry point.

On a regular Vue app using the Laravel included files, this would be your ./resources/js/app.js.

Import the filters file there:

import Vue from 'vue';

// other imports, if any

import './filters.js';

// other plugin initialization, if any

window.app = new Vue({
    el: '#app',
    // ... other root element code
});

Here I am assuming you saved the filters.js file in the same folder as your app.js file.

Step 3

Now you can use these new filters in your components by using the pipe | operator:

              <div class="item-description">
                <h4>{{ produit.nom }}</h4>
                <p>{{ produit.prix | integer }} FCFA</p>
              </div>

Here I used the integer filter, but you can experiment with the other ones. The currency filter will already append (or prepend, depending on the locale) the currency symbol. But as in your template you are using FCFA instead of just CFA maybe you'd prefer to use the integer or decimal ones.

Step 4 & References

Compile your code and test your changes.

Note that filters is one Vue feature that was removed from version 3 (I wish they wouldn't, they are so useful).

So if you are using Vue 3 you can add those filters as a component's methods and call them as regular methods.

I added a link to Vue 3 migration guide about how to "migrate" filters from Vue 2 to Vue 3.

References:

https://vuejs.org/v2/guide/filters.html

https://v3.vuejs.org/guide/migration/filters.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat

https://www.fincher.org/Utilities/CountryLanguageList.shtml

http://www.lingoes.net/en/translator/langcode.htm

Hope it helps.

1 like

Please or to participate in this conversation.