javascriptjsonreactjscommerce

Total is displaying NaN in Cart


So the problem I am currently facing is this. I have a Cart logic located in the CartContext. Everything works except the total located in Checkout Component, it should sum all the prices of the items in the cart, but instead is displaying NAN. Here is the link to the CodeSandbox for a better understanding https://codesandbox.io/s/frosty-sound-5y7pg?file=/src/CartItem.js:1486-1494.Please comment if something is wrong with sandbox

Cart Context

import React from "react";
function getCartFromLocalStorage() {
  return localStorage.getItem("cart")
    ? JSON.parse(localStorage.getItem("cart"))
    : [];
}

const CartContext = React.createContext();

function CartProvider({ children }) {
  const [cart, setCart] = React.useState(getCartFromLocalStorage());
  const [total, setTotal] = React.useState(0);
  const [cartItems, setCartItems] = React.useState(0);

  React.useEffect(() => {
    localStorage.setItem("cart", JSON.stringify(cart));

    let newTotal = cart.reduce((total, cartItem) => {
      return (total += cartItem.amount * cartItem.price);
    }, 0);
    newTotal = parseFloat(newTotal.toFixed(2));
    setTotal(newTotal);
    // cart items
    let newCartItems = cart.reduce((total, cartItem) => {
      return (total += cartItem.amount);
    }, 0);
    setCartItems(newCartItems);
  }, [cart]);

  // global functions
  const removeItem = id => {
    setCart([...cart].filter(item => item.id !== id));
  };
  const increaseAmount = id => {
    const newCart = [...cart].map(item => {
      return item.id === id
        ? { ...item, amount: item.amount + 1 }
        : { ...item };
    });
    setCart(newCart);
  };
  const decreaseAmount = (id, amount) => {
    if (amount === 1) {
      removeItem(id);
      return;
    } else {
      const newCart = [...cart].map(item => {
        return item.id === id
          ? { ...item, amount: item.amount - 1 }
          : { ...item };
      });

      setCart(newCart);
    }
  };
  const addToCart = book => {
    const { id, image, by, bookName,RegularPrice } = book;
    const item = [...cart].find(item => item.id === id);

    if (item) {
      increaseAmount(id);
      return;
    } else {
      const newItem = { id, image, by, bookName, RegularPrice, amount: 1 };
      const newCart = [...cart, newItem];
      setCart(newCart);
    }
  };
  const clearCart = () => {
    setCart([]);
  };
  return (
    <CartContext.Provider
      value={{
        cart,
        cartItems,
        total,
        removeItem,
        increaseAmount,
        decreaseAmount,
        addToCart,
        clearCart
      }}
    >
      {children}
    </CartContext.Provider>
  );
}

export { CartContext, CartProvider };

Cart Item

import React, { useContext } from "react";
import {Link, useHistory } from 'react-router-dom'
import { CartContext } from "../../context/cart";
import { FaAngleDown, FaAngleUp } from "react-icons/fa";
import Checkout from "./Checkout";
export default function CartItem({ id, image,bookName, RegularPrice, by, amount }) {
  const { removeItem, increaseAmount, decreaseAmount } = React.useContext(
    CartContext
  );
  return (
      <div id={id} className="cart__item">
          <img className='cart__image' src={image}  />
        <div className='cart__itemdesc'>
         <h4>{bookName}</h4>
        <h6 className='cart__by'>By: {by}</h6>
        <button
          className="cart__removebtn"
          onClick={() => {
            removeItem(id);
          }}
         >
          Remove
        </button>
        <div>
        <button
          className="cart-btn amount-btn"
          onClick={() => {
            increaseAmount(id);
          }}
        >
          <FaAngleUp />
        </button>
        <p className="item-amount">{amount}</p>
        <button
          className="cart-btn amount-btn"
          onClick={() => {
            decreaseAmount(id, amount);
          }}
        >
          <FaAngleDown />
        </button>
      </div>
        </div>  
        <span className='circle'><span className='circleone'></span></span>
        <span className='cart__regular'>{RegularPrice}</span>      
        <div>   
        <Checkout />
        </div>    
    </div>
  );
}

Checkout

import React,{useContext} from 'react'
import { CartContext } from '../../context/cart'
import {Link, useHistory } from 'react-router-dom'
import EmptyCart from './EmptyCart';

const Checkout = () => {
  const history = useHistory()
  const {cart, total} = useContext(CartContext)
  if (cart.length === 0) {
    return <EmptyCart />;
  }

    return (
        <div className='checkout'>
            <h2>Summary</h2>
            <h2>Subtotal : ${total}</h2>
            <Link to='/stripecontainer' className='checkout__btnOne'>Proceed to Checkout</Link>            
        </div>
    )
}

export default Checkout

Solution

  • You're dealing with bad data. The proper response in this case is to request sanitized data from whoever is in charge of the backend. If the person in charge of the backend is still you, you should fix this in backend, not in frontend.

    Long story short, don't use parseFloat(). When you have to cast strings to numbers, a safer bet is using Number(val) (or its shorthand: +val). (it's still a bet, though...)

    Another minor thing: you're adding items to cart with amount: 0. Shouldn't it be 1?

    setCart(
      cart.concat({
        amount: 1,
        price: +book.RegularPrice,
        ...book
      })
    );
    

    And one last note: what you have now will override your assignments to amount and price if book already has any of those properties set. If you want to have complete control (and confidence) over the assignment, you should spread the object first, then assign:

    setCart(
      cart.concat({
        ...book,
        amount: 1,
        price: +book.RegularPrice
      })
    );
    

    See it here.