프로젝트 이모저모/HoduMarket 프로젝트

오픈마켓 버그) 장바구니에 상품을 담을 경우 TotalPrice 컴포넌트 업데이트 안됨

Ella Seon 2023. 8. 18. 18:54

0. 버그 현황

- 리액트쿼리로 리팩토링 도중 상품을 장바구니에 담았을때 TotalPrice 컴포넌트의 총 상품금액과 배송비가 업데이트가 안되는 문제가 발생했다.

const useFetchCartItems = (token: string) => {
  const { data: cartList } = useFetchCartList(token);
  // console.log(cartList);

  const productIds = cartList.results.map((item: any) => {
    return item.product_id;
  });

  // console.log("Product IDs:", productIds);

  const queryInput = productIds.map((productId: string) => {
    return {
      queryKey: ["cartProductDetail", productId],
      queryFn: () => productAPI.fetchProductDetail(productId),
    };
  });
  // console.log("Query Input:", queryInput);

  const cartItemsResponses = useQueries({ queries: queryInput });

  // 결과를 기반으로 cartItems 생성
  const cartItems = cartList?.results?.map((item: CartType, index: any) => ({
    ...item,
    productDetail: cartItemsResponses[index]?.data,
  }));

  // 상품금액
  const PriceArray = cartItems.map(
    (item: CartItemType) => item.productDetail?.price
  );
  const initialTotalPrice = PriceArray.reduce(
    (accumulator: number, currentValue: number) => accumulator + currentValue,
    0
  );

  //배송비
  const deliveryFeeArray = cartItems.map(
    (item: CartItemType) => item.productDetail?.shipping_fee
  );
  const initialDeliveryFee = deliveryFeeArray.reduce(
    (accumulator: number, currentValue: number) => accumulator + currentValue,
    0
  );

  return { cartItems, initialTotalPrice, initialDeliveryFee };
};

export default useFetchCartItems;
function CartPage() {
  const token = useAppSelector((state: RootState) => state.login.token) || "";
  // 장바구니 정보 가져오기
  const { cartItems, initialTotalPrice, initialDeliveryFee } =
    useFetchCartItems(token);

  const [totalPrice, setTotalPrice] = useState(initialTotalPrice);
  const [totalDeliveryFee, setTotalDeliveryFee] = useState(initialDeliveryFee);


  return (
    <S.CartPageLayout>
      <S.CartText>장바구니</S.CartText>
      <S.MenuUl>
        <CheckCircleBtn
          isChecked={isAllChecked}
          onChange={handleToggleAllCheckbox}
        />
        <li>상품정보</li>
        <li>수량</li>
        <li>상품금액</li>
      </S.MenuUl>
      {cartItems.map((cartItem: CartItemType, index: number) => (
        <CartItem
          key={cartItem.cart_item_id}
          cartItemId={cartItem.cart_item_id}
          quantity={cartItem.quantity}
          cartItem={cartItem.productDetail}
          isChecked={isCheckedArray[index]}
          onToggle={() => handleToggleCheckbox(index)}
          totalPrice={totalPrice}
          totalDeliveryFee={totalDeliveryFee}
          setTotalPrice={setTotalPrice}
          setTotalDeliveryFee={setTotalDeliveryFee}
        />
      ))}
      {cartItems.length === 0 ? (
        <S.NoItemBox>
          <p>장바구니에 담긴 상품이 없습니다.</p>
          <small>원하는 상품을 장바구니에 담아보세요!</small>
        </S.NoItemBox>
      ) : (
        <TotalPrice
          totalPrice={totalPrice}
          totalDeliveryFee={totalDeliveryFee}
        />
      )}
    </S.CartPageLayout>
  );
}

export default CartPage;

 
장바구니를 조회했을 때 useFetchCartItems 라는 리액트쿼리로 장바구니 정보를 불러온 후 
총상품금액과 배송비를 useState에 담았다. 
왜냐하면 setState를 사용하기 위해서 useState를 담았다. CartPage 컴포넌트 자식인 CartItem 에서 삭제기능과 수량조절에 맞춰 TotalPrice 가 바뀌어야하기 때문에
상태끌어올리기 기법으로 CartPage에서 useState를 지정해주고 자식에 setState를 props로 넘겨주어 삭제, 수량조절을 했을때 setState로 금액변경해서 TotalPrice 컴포넌트에 적용되길 바랬었다. 
 
더불어 체크박스를 클릭하고 해제 할때 TotalPrice 가격이 달라져야 하니 useState를 담았다. 
 
 
결론은 setState를 사용하기 위해 useState에 담았다. 
 
하지만...상태에 담아버리니 초기에 가격이 렌더링 되지 않았다.
 


1. 해결과정

1) useQuery 로 반환된 데이터를 그대로 보여줄때는 괜찮았다.

TotalPrice 컴포넌트에 useState로 담은 totalPrice, totalDeliveryFee를 담을 때는 초반에 렌더링이 되지 않았지만,
react-query 로 받아온 값을 그대로 props로 전달해주니 상품을 장바구니 페이지에 담았을 때 바로 업데이트가 되었었다.
 

      <TotalPrice
          totalPrice={initialTotalPrice}
          totalDeliveryFee={initialDeliveryFee}
        />

 

2) useState에 문제가 있는 것 같았다.

콘솔에 리액트쿼리로 반환되는 initialTotalPrice 와 useState 값인 totalPrice 를 출력해보니
initialTotalPrice  useQuery 반환값은 처음에는 데이터를 받아오면서 Nan이 되다가 82223원으로 토탈 값이 출력되는 것을 볼수있었다.
하지만 useState의 값은 그대로 2223에서 멈춰있는걸 볼수있다.
 
 

 

3) useState의 초기값은 'CartPage' 컴포넌트가 처음 렌더링 될때만 설정된다.

- initialTotalPrice 가 처음 렌더링 될때 NaN으로 설정되니 그 값이 초기값으로 설정이 된것이다. react-query 의 서버데이터는 비동기적으로 데이터를 받아오기에 처음에는 데이터가 없을수도 있어서 'NaN'으로 표시되지만, 데이터가 받아와지면 또 재렌더링이된다. 
- 따라서 처음에 NaN 으로 설정되버리니 totalPrice 도 'NaN' 으로 설정돼서 TotalPrice 컴포넌트에 가격이 반영이 안된것이다. 
 

4) useEffect 로 initialTotalPrice, initialDeliveryFee가 변경될때 상태를 업데이트 하도록하자

  const [totalPrice, setTotalPrice] = useState(0);
  const [totalDeliveryFee, setTotalDeliveryFee] = useState(0);

  useEffect(() => {
    setTotalPrice(initialTotalPrice);
    setTotalDeliveryFee(initialDeliveryFee);
  }, [initialTotalPrice, initialDeliveryFee]);

 
 
 
 

결론 : useState 값은 처음 렌더링될때 초기값이 설정된다. react-query 는 비동기적이어서 처음에는 데이터가 없을 수 있다.