0

In this project I'm attempting to make a simple Online Shop using React JS. The issue I'm having involves setState and lifecycle. Whenever an item in added to a cart its put into an array of objects. The user can also add more then one of the same item to their shopping cart.

The issue I'm having involves calculating the price of an item. Whenever the user put's an item into their cart the price calculates to 0, but when they add another item the price adds up to having two items.

User adds One $3.00 item the total price comes up to $0.00

User adds Two $3.00 items the total price comes up to $6.00

I can't seem to figure out why it's behaving this way.

Disclaimer. This project is not going to be deployed. This project was created to utilize my skills I've already picked up.

import React from 'react'

import StoreSelection from './StoreSelection';


const Store = ({inventory,cart,setCart,setPrice}) => {
  const cart_addItem = (newItem) =>{

    let duplicate = false;
    let indextochange = 0;

    for(let item in cart){
      if(cart[item].id === newItem.id){
        duplicate = true;
        indextochange = item;
      }
    }
    
    if(duplicate===true){
      let newList = [];
      for(let item in cart){
        newList[item] = cart[item];
      }
      newList[indextochange].quantity +=1;
      setCart(newList);
    }else{
      newItem.quantity = 1;
      setCart([...cart,newItem]);
    }
    calculatePrice(cart);
  }

  const calculatePrice = (list) =>{
    let newprice = 0;
    console.log(list);
    for(let item in list){
      console.log(list[item].quantity);
      newprice = (parseFloat(list[item].price) * list[item].quantity) + newprice;
    }
    setPrice(newprice);
  }
  
  return (
    <div>
      <StoreSelection inventory={inventory} cart_addItem={cart_addItem}/>
    </div>
  )
}

export default Store
  • The problem here is "just after the state is updated". The state is _not_ updated until the next render pass (because render passes are triggered _by_ the state having been updated =) You called the function to schedule a state update, but that will not happen until _after_ the current render pass has finished. – Mike 'Pomax' Kamermans Jul 10 '22 at 00:28
  • Price actually isn’t state at all, it’s just the value of all items in the cart (derived state, not state itself). The (contents of the) cart is the only thing that’s state. Remove the state for price and the solution becomes apparent. – Adam Jenkins Jul 10 '22 at 00:41
  • 1
    I wouldn’t mark this as a duplicate, because the real problem is that the OP is using a state (price) where they shouldn’t be. – Adam Jenkins Jul 10 '22 at 00:44

1 Answers1

0

I left inline comments in the refactored code below. If something is unclear, feel free to ask for clarification in a comment to this answer.

Reference documentation for the hooks used:

import {default as React, useCallback, useEffect} from 'react';
import StoreSelection from './StoreSelection';

// This doesn't need to be created in your component.
// Creating it once, outside the component, is an optimization:
function calculatePrice (cart) {
  let updatedPrice = 0;
  // console.log(list);
  for (const item of cart){
    // console.log(item.quantity);
    updatedPrice += parseFloat(item.price) * item.quantity;
  }
  return updatedPrice;
}

export default function Store ({cart, inventory, setCart, setPrice}) {
  // You can calculate the new price every time the cart changes by updating it
  // in response to changes in the cart state:
  useEffect(() => setPrice(calculatePrice(cart)), [cart]);
  // However, if you don't manually update the price state in other places,
  // then it doesn't need to be state at all. You can just create it this way
  // in the parent component where it was defined:
  // const price = useMemo(() => calculatePrice(cart), [cart]);

  const cart_addItem = useCallback(item => {
    // In your question's code it appears as though it is not certain that
    // the added item's `quantity` property is set. If that's not the case,
    // then you can simply delete the following statement. If it is
    // `undefined` or `null`, then this will set it to `1`:
    item.quantity ??= 1;

    // Use the functional form of the argument to `setCart`. This way,
    // it will not depend on an external state value (so it will
    // always be current and also won't be needed in the dependency array):
    setCart(cart => {
      const index = cart.findIndex(currentItem => currentItem.id === item.id);
      const updatedCart = [...cart];
      if (index >= 0) {
        // Be sure not to mutate the item object within in the cart state:
        const updatedItem = {...updatedCart[index]};
        updatedItem.quantity += item.quantity;
        updatedCart[index] = updatedItem;
      }
      else updatedCart.push(item);
      return updatedCart;
    });
  }, [setCart]);
  
  return (
    <div>
      <StoreSelection {...{cart_addItem, inventory}}/>
    </div>
  );
}

jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • 1
    Not only did you solve my problem, but you showed me material I never seen before. Thank you for seeing informative while being practical! Thank You! – Jason Roman Jul 10 '22 at 01:53
  • @JasonRoman I'm glad you learned something today. Teach others! – jsejcksn Jul 10 '22 at 02:00