[Magazine K Project] 장바구니 기능

front-end developer·2022년 11월 3일
0

장바구니 기능

2-1. 장바구니 모달창 슬라이드 기능

우리 페이지는 네비게이션에 Cart라는 탭으로 장바구니 기능이 추가되어 있다.

Cart를 누르거나 상품리스트페이지에서 AddCart를 누르거나 상세페이지 장바구니담기를 누르면 값이 업데이트 되면서 모달창이 오른쪽으로부터 나온다.

  • 우선, 모달창이 오른쪽에서 나타나는 기능을 추가하기 위해 카트모달창을 오른쪽 공간에 숨겨뒀다.
  • cart를 클릭하면 모달창이 나타나게 해야하는데, 이런 애니메이션 기능을 처음 구현해봐서 구글링을 해봤다.
  • isClicked라는 boolean state값과 함께 앤드연산자로 classname을 부여하는 방식으로 구현하였다.
    <div className={['cartModal', isClickedCart && 'cartModalOn'].join(' ')}>
  • isclickedCart 값이 false 이면 className은 cartModal false가 되고, true이면 cartModal cartModalOn가 된다.
  • 아래 Scss 값을 다음과 같이 부여했다.
    .cartModal {
      position: fixed;
      transition: right 0.1s linear;
      right: -22rem;
    
    }
    .cartModalOn {
      right: 0;
    }
  • 즉, 모달창이 나타나면 right값이 -22rem에서 0으로 0.1s의 시간동안 linear한 transition이 일어난다.
  • isClickedCart 값은 cart 메뉴탭을 클릭하면 변경되는 로직이다.

2-2. 장바구니 각 상품의 수량 관리 및 총 수량 관리

  • 장바구니 모달창에는 각 상품의 수량을 증가, 감소시킬 수 있다.

  • 각각의 상품을 컴포넌트화하고 그 안에서 state값으로 수량을 관리하면 각각의 수량을 관리할 수 있다.

  • 그러나 총 수량으로 관리하기 위해서는 자식 컴포넌트의 액션이 부모 컴포넌트의 값에 영향을 주도록 로직을 구현해야했다.

    • 이러한 이유로 부모 컴포넌트에서도 수량을 의미하는 state를 선언해줬고, 수량이 증가/감소 하는 함수도 선언해줬다.
    • 이 함수를 자식 컴포넌트에게 props로 넘기고 자식 컴포넌트에서 수량을 바꾸면 동작하는 함수에 부모에서 물려받은 함수(부모 컴포넌트에서의 수량이 증가/감소 하는 함수)를 넣어줬다.
    • 이를 통해 자식 state값과 부모 state값이 동시에 증가, 감소하는 로직을 구현할 수 있었다.
  • 부모 컴포넌트 총 수량 조절

  const [totalOrderNum, setTotalOrderNum] = useState(0);

  const decreaseTotalOrderNum = () => {
    if (totalOrderNum < 2) {
      setTotalOrderNum(1);
    } else if (totalOrderNum < cartData.length + 1) {
      setTotalOrderNum(cartData.length);
    } else {
      setTotalOrderNum(totalOrderNum => totalOrderNum - 1);
    }
  };

  const increaseTotalOrderNum = () => {
    setTotalOrderNum(totalOrderNum => totalOrderNum + 1);
  };
  • 자식 컴포넌트 각 수량 조절
const ProdInCart = ({
  cartData,
  decreaseTotalOrderNum,
  increaseTotalOrderNum,
}) => {
  
const [orderNum, setOrderNum] = useState(cartData.quantity);

const increaseOrderNum = () => {
    setOrderNum(prev => prev + 1);
    increaseTotalOrderNum();
    patchCartData('addition');
  };

const decreasseOrderNum = e => {
    if (orderNum < 2) {
      setOrderNum(1);
    } else {
      setOrderNum(prev => prev - 1);
      decreaseTotalOrderNum();
      patchCartData('subtraction');
    }
  };

  useEffect(() => {
    setOrderNum(cartData.quantity);
  }, [cartData]);
}

2-3. 장바구니 각 상품의 금액 계산 및 모든 상품의 총액 계산

  • 상품의 수량 값은 자식요소의 -,+ 버튼을 누르면 값이 1씩 변화하므로 단순히 number state값으로 구현이 가능했다.
  • 하지만, 가격의 경우 상품마다의 금액이 다르기 때문에 수량을 관리하는 로직과는 다르게 구현해야 했다.
  • 각 자식 컴포넌트마다 값을 다르게 관리해야 하므로 state를 배열로 관리해야겠다라고 생각했다.
    • 빈 배열을 만들고, 배열의 길이를 카트에 담긴 상품의 갯수만큼 만들어줬다. 이때, 페이지네이션에서 사용했을 때와 마찬가지로 Array()메소드를 사용했다.
    • 이 배열을 각각 자식 컴포넌트에 넘기면서 동시에 map함수의 2번째 인자인 index값도 같이 넘겨줬다.
    • 자식 컴포넌트에서 state[index] 를 통해 state의 배열 중 자신에게 해당하는 요소만 수정할 수 있다.
    • 즉, 각각의 컴포넌트에서 state[index] 값을 수정하면 빈 배열이었던 state는 각각의 금액으로 구성된 배열이 완성된다.
    • 이 배열을 부모 컴포넌트에서 useEffect를 통해 총합을 계산하면 카트에 담긴 상품의 총액을 구할 수 있다.

⇒ 자세한 설명은 아래 글을 참고하면 된다. https://www.notion.so/d89aa2af4ad04c5c82a9b45b748f7481

2-4. 토큰이 있을때만 API에 요청

문제 인식

  • 어느정도 기능구현이 완료되었을 쯤, 테스트용 새로운 브랜치를 만들어 각 기능 브랜치들을 통합하였다.
  • 이 테스트 브랜치로 백엔드와 실질적인 통신을 해보려고 했는데, 메인 페이지에 들어서자마자 콘솔에 에러가 찍히면서 화면에 아무것도 나오지 않았다.
    • 네비게이션 바의 카트 모달창 컴포넌트에서 발생한 에러였고, undefined 값에 대해 map 메서드를 적용할 수 없다는 에러였다.

원인 파악

map 메서드를 적용할 배열의 값이 왜 undefined인지 로직을 살펴보았다. 메인페이지에 들어서자마자 네브바 컴포넌트가 호출되고 네브바 안의 카트 컴포넌트는 카트에 담긴 데이터리스트를 요청하게 되는데, 사실 백엔드에서는 카트에 담긴 데이터를 보낼 때 토큰이 있는 경우에만 데이터를 보내던 것이었다.

⇒ 즉, API에 호출해서 받은 값은 없는데 이 값을 가지고 map함수를 돌리다가 에러가 난것이었다.

문제 해결

로그인이 되어 있지 않을 때, 즉 로그인 토큰이 발급되어 있지 않을 때는 카트 데이터를 요청하지 않도록 로직을 수정하였다. ⇒ 데이터를 가져오는 함수에 조건문 추가.

const getCartData = () => {
    if (token) {
      fetch('http://10.58.3.49:8000/orders/cart', {
        method: 'GET',
        headers: {
          AUTHORIZATION: token,
        },
      })
        .then(res => res.json())
        .then(res => {
          if (res.message === 'EMPTY CART') {
            return;
          } else {
            setCartData(res.result[0].product);
            let sum = 0;
            res.result[0].product.forEach(product => {
              sum = sum + product.quantity;
            });
            setTotalOrderNum(sum);
          }
        });
    }
  };

2-5. 모달창을 외부 컴포넌트에서 조절하기

처음엔 매거진B 사이트와 같이 add to cart 버튼을 누르면 모달창이 나오고 장바구니에 상품이 추가되어 나오도록 로직을 구현하려 했다.

그러나, 장바구니 모달창은 공통 컴포넌트인 Nav.js에 속해있고 add to cart버튼은 외부 컴포넌트 리스트페이지와 상세페이지 컴포넌트에 속해있다.

const Router = () => {
  return (
    <BrowserRouter>
      **<Nav />**
      <Routes>
        **<Route path="/Products/:product_id" element={<ProductDetail />} />
        <Route path="/ProductList" element={<ProductList />} />**
      </Routes>
      <Footer />
    </BrowserRouter>
  );
};

따라서 add to cart 버튼 클릭 시, 모달창을 띄우기 위해서는 외부 컴포넌트간의 데이터 이동이 가능해야했다.

  • 이를 구현하기 위해 구글링한 결과 모든 컴포넌트간의 state 공유가 가능한 context API 혹은 react Redux 라이브러리 같은 기능이 있었는데, 중요한건 프로젝트 마감 시간이 얼마 남지 않았고 이번 프로젝트는 라이브러리를 사용하지 않기로 했었다.

결국, add to cart 버튼 클릭 시 모달창은 나오지 않고 장바구니에 추가됐다는 알람만 뜨게 하는 형식으로 방향을 수정했다.

  • 유저는 카트에 상품이 추가됐다는 알람을 확인하고 네브바의 Cart탭을 클릭하면 isClickedCart 라는 state값이 바뀌어 모달창이 나온다.

  • Cart.js의 useEffect는 API로부터 카트 데이터리스트들을 불러오는데, 2번째 인자값으로 isClickedCart 를 추가해 모달창이 작동하면 자동으로 데이터리스트를 업데이트해 원하는 구현이 가능했다.

  • 추가사항) Router에서 모달창을 조절하는 useState를 선언하고 각 컴포넌트에 props로 내리는 방법을 이용해 원하던 기능을 구현할 수 있었다. Redux 라이브러리를 사용하지 못하는 현상황에서 대처방안으로 사용하였다.
const Router = () => {
  const [modalState, setModalState] = useState(false);
  return (
    <BrowserRouter>
      **<Nav modalState={modalState} setModalState={setModalState} />**
      <Routes>
        **<Route
          path="/Products/:product_id"
          element={<ProductDetail setModalState={setModalState} />}
        />
        <Route
          path="/ProductList"
          element={
            <ProductList
              modalState={modalState}
              setModalState={setModalState}
            />
          }
        />**
      </Routes>
      <Footer />
    </BrowserRouter>
  );
};
profile
학습한 지식을 개인적으로 정리하기 위해 만든 블로그입니다 :)

0개의 댓글