[Book Buddy / Error Note] 장바구니에 상품이 두 번까지 담기는 문제

직면한 문제

 

프로젝트를 하면서 장바구니에 한 번만 상품을 담을 수 있도록 했다. 

이는 장바구니에서 상품의 id를 사용하기 때문에 두 번 등록되어 id 가 겹치는 상황을 방지하기 위함이다.

 

그런데 구현 후 동작해보니 상품페이지에서 장바구니 담기 버튼이 두 번까지 작동한다. 

아래 영상을 보면 '상품을 장바구니에 추가했습니다' 라는 문구가 두 번까지 나오고 세 번째부터 '이미 추가한 상품입니다' 문구가 나오는 모습을 볼 수 있다. 오른쪽 콘솔에는 장바구니 현재 리스트가 찍히는데 두 번까지 빈 배열로 찍힌 후 세 번째부터 해당 도서가 리스트에 추가되는 모습을 볼 수 있다.

 

올바르게 동작하지 않는 모습

 

원래 코드

 

  const addCartHandler = () => {
    getCartList(setCartList); // api 요청을 통해 setCartList로 cartList 업데이트
    if (accessToken) {
      if (
        cartList.filter(v => {  // cartList로 filter 작업
          return v.book.id === bookId;
        }).length > 0
      ) {
        alert('이미 추가한 상품입니다');
      } else {
        postCartItem(detailInfo);
        bookId &&
          setQuantityList([
            ...quantityList,
            {
              id: bookId,
              quantity: 1,
            },
          ]);
        alert('상품을 장바구니에 추가했습니다');
      }
    } else {
      alert('⚠️ 먼저 로그인해 주세요');
    }
  };

 

원래 코드는 이랬다. addCartHandler라는 하나의 함수 안에서 getCartList()로 api를 요청하며 cartList를 업데이트 하고, 그 다음 코드에서 cartList를 사용해 filter 작업을 한다. 

 

어찌보면 기본중의 기본이지만, 정신없이 코드를 짜다 보니 이렇게 된 것 같은데 다음부턴 처음부터 잘 짜길 바라며 이 참에 한 번 짚고 넘어간다.

 

해결

 

원인은 getCartList함수가 비동기식이기 때문이다. JavaScript는 비동기 작업의 경우 해당 코드가 느리면 그 코드가 완전히 실행 완료되기 전에 다음 코드로 넘어가버린다. 특히 내 코드처럼 서버를 통해 데이터를 받아오는 api의 경우 속도가 느리다. 그래서 내 코드는 api 요청 후 cartList가 업데이트 되기를 기다리지 않고 이전 cartList로 filter작업이 실행되어버린 것이다. 

 

 

  const addCartHandler = () => {
    if (accessToken) {
      getCartList().then((data: CartListType[]) => { // api를 받아온 후 다음 동작 실행
        if (
          data.filter(v => { // then으로 받아온 data 바로 활용해 filter 작업
            return v.book.id === bookId;
          }).length > 0
        ) {
          alert('이미 추가한 상품입니다');
        } else {
          postCartItem(detailInfo);
          bookId &&
            setQuantityList([
              ...quantityList,
              {
                id: bookId,
                quantity: 1,
              },
            ]);
          alert('상품을 장바구니에 추가했습니다');
        }
      });
    } else {
      alert('⚠️ 먼저 로그인해 주세요');
    }
  };

 

원인을 알면 해결법은 간단하다. 사실 cartList 는 상태관리 라이브리를 통해 다른 컴포넌트에서도 사용되고 있기 때문에 무의식적으로 사용했던건데, 이 컴포넌트에서는 cartList를 업데이트 하지 않아도 다른 곳에 영향을 주지 않으니 과감히 삭제했다. 또한 getCartList promise의 .then 콜백 안으로 코드를 옮겨서 데이터를 온전히 받아온 후 다음 코드가 실행되게 했다. 

 

 

올바르게 동작하는 모습

 

의도한대로 동작하는 모습을 볼 수 있다.