#3.Project(개발회고-장바구니페이지)

Seongjae Hwang·2021년 12월 11일
0

weasly-project

목록 보기
3/5

장바구니페이지는 정말 기억에 오래 남을것 같다. API GET밖에 몰랐는데 PATCH, DELETE, POST등 다양한 매서드를 사용해 보며, 이런게 통신이구나 하고 알 수 있었고, 제품을 추가, 감소, 삭제 등의 기능을 해보면서 state관리법 및 불변성이라는 것을 몸소 깨닫게 되었던것 같다.
정말 이 시간은 fetch 후 message 결과에 따른 다른 랜더링을 보여주는 과정속에서 어떠한 리액트 강의들보다 state 및 불변성을 이해하는데 좋았던것 같다.

장바구니 페이지

컴포넌트


장바구니페이지에서는 장바구니 컨디션에 따라 Cart, EmptyCart, LoadingCart로 나누었고 각 Cart마다 필요한 상품리스트인 List와 총가격 부분인 Price 컴포넌트로 세분화해주었다.

장바구니 컨디션



우선, 장바구니를 구현하면서 정말 다양한 fetch함수를 사용해볼 수 있는 값진경험이 있었는데 이해한 것을 바탕으로 fetch의 상태에 따른 로딩중인 화면을 만들어 내고 싶었다. 따라서 위와 같이 fetch상태와 data의 상태에 따라 다른 랜더링을 구현하였다.
랜더링하는 과정은 처음에 GET요청한 데이터가 비어있는지를 확인하고, true이면 를 반환한다. 그다음에는 페이지가 로딩중인지 확인하고 true이면 를 반환하고 false이면 Cart State를 랜더링해주었다.

상품 추가 및 감소

가장 시간이 많이 걸린 부분중 하나였다. 하지만 이를 통해 state의 성질을 확실하게 알 수 있게 되었다.

 const handlePlus = () => {
    if (list.amount === 99) {
      alert('제품을 더 이상 추가할수 없습니다.');
      return;
    }
    fetch(`${API.CART}/${list.cart_id}`, {
      method: 'PATCH',
      headers: {
        Authorization: `Bearer ${sessionStorage.getItem('access_token')}`,
      },
      body: JSON.stringify({
        amount: list.amount + 1,
      }),
    })
      .then(res => res.json())
      .then(data => {
        if (data.MESSAGE === 'INVALID_TOKEN') {
          alert('로그인이 필요합니다!');
          navigate('/signin');
          return;
        }
        data.MESSAGE === 'SUCCESS'
          ? increaseCartItem()
          : alert(data.MESSAGE);
      })
      .catch(error => alert(error));
  };

먼저, 상품 추가 버튼을 누르면 아래와 같이 PATCH요청을 보내는 handlePlus함수를 실행시킨다. 최대 수량을 99개로 지정했기때문에 프론트쪽에서 99개이상은 PATCH요청을 보내지 못하도록 막아주고, 그 이하라면 API요청할때 주소에 cart_id를 함께 보내고, headers에는 유저의 토큰을, body에는 JSON형태로 변환하여 추가된 amount를 보내주었다. 그리고 백엔드에서 보내는 메세지에 따라 'INVALID_TOKEN'이면 로그인 페이지로 보내고, 'SUCCESS'이면 props로 전달한 increaseCartItem이라는 state를 랜더링 시켜주는 함수를 실행시켜주었다.

const increaseCartItem = targetIndex => {
    const newCartItem = {
      ...cart[targetIndex],
      amount: cart[targetIndex].amount + 1,
    };
    const newCart = cart.map((item, itemIndex) => {
      if (itemIndex === targetIndex) {
        return newCartItem;
      } else {
        return item;
      }
    });
    setCart(newCart);
  };

...

{cart.map(function (list, index) {
            return (
              <List
                key={list.product_id}
                list={list}
                cart={cart}
                setCart={setCart}
                setEmpty={setEmpty}
                eraseCartItem={() => eraseCartItem(list.product_id)}
                increaseCartItem={() => increaseCartItem(index)}
                decreaseCartItem={() => decreaseCartItem(index)}
              />
            );
          })}

그 다음, 부모에서 선언된 increaseCartItem이라는 함수에서는 index를 인자를 받고, 이를 활용하여 amount가 늘어난 새로운 객체를 선언하고, map을 사용하여 itemIndex와 targetIndex가 일치하는 곳은 newCartItem를 반환하고 일치하지 않으면 item을 반환하도록하여 새로운 newCart state를 만들고 이를 setCart(newCart)로 덮어씌워주었다. 이렇게 하면 랜더링이 되는데 그 이유는 newCart는 기존과 전혀 다른 메모리값을 지닌 state이기 때문이다.

상품 삭제

const handleDelete = () => {
    window.confirm(`${list.category_name}을 삭제 하시겠습니까?`)
      ? fetch(`${API.CART}?id=[${list.cart_id}]`, {
          method: 'DELETE',
          headers: {
            Authorization: `Bearer ${sessionStorage.getItem('access_token')}`,
          },
        })
          .then(res => res.json())
          .then(data => {
            if (data.MESSAGE === 'INVALID_TOKEN') {
              alert('로그인이 필요합니다!');
              navigate('/signin');
              return;
            }
            data.MESSAGE === 'DELETED' ? eraseCartItem() : alert(data.MESSAGE);
          })
          .catch(error => alert(error))
      : setCart(cart);
  };

상품 삭제역시 제품 삭제버튼을 클릭하면 confim창을 통해 삭제를 할것인지 물어보고 맞다면 cart_id를 API와 함께 DELETE 요청을 보낸다. 그리고 headers에는 유저의 토큰을 보내고, data.MESSAGE가 'INVALID_TOKEN'이면 로그인 페이지로 보내고 data.MESSAGE가 'DELETED'이면 부모에서 선언하여 props로 전달한 eraseCartItem이라는 state를 랜더링해주는 함수를 실행하였다.

 const eraseCartItem = productId => {
    const filteredCart = cart.filter(item => {
      return item.product_id !== productId;
    });

    if (filteredCart.length === 0) {
      setEmpty(true);
    } else setCart(filteredCart);
  };

list.product_id를 인자로 받는 eraseCartItem함수에서는 먼저, filteredCart라고 하는 filter매서드를 활용하여 인자로 받은 productID와 cart의 item.product_id가 일치하지 않는 새로운 배열을 저장하고, 만약 그 배열이 빈배열이 된다면 empty state를 true값으로 변경하여 빈카트 레이아웃을 보여주고 아니라면 setCart에 filteredCart를 넣어 새로운 랜더링을 보여주었다. 이 부분에서 내가 한가지 실수한 부분이 있는데,
처음에 나는 '삭제'라는 키워드에 집중되어 배열에서 삭제 시키는 매서드인 splice를 활용하여

const newCartItem = {
	…cart.splice(targetIndex, 1),
};

위와 같은 배열을 만들었는데, splice는 원본 배열을 훼손시키는 매서드인것을 생각하지 않고 사용했었다. 이는 cart state자체의 값을 바꾸기 때문에 불변성에 어긋나는 행위였고, filter매서드를 통해 불변성을 지키며 새로운 배열을 만들어 낼 수 있었다.

profile
Always Awake

0개의 댓글