ToyProject(더다주) cart(2)

노영완·2023년 7월 23일
0

ToyProject(더다주)

목록 보기
6/13

선택에 따른 선택삭제 선택주문 전체선택 기능들은 가장 시간이 많이들고 답답했던 기능들이었다.

선택삭제

첫 생각

  1. 서버에서는 deleteMany를 이용할 것. 전체 데이터를 아예 주어서 데이터를 삭제해야 하나? deleteMany({data1}, {data2}) 이런식으로 하지만 deleteMany 사용예제에서 이러한 경우는 찾아볼 수 없었다
  2. 그럼 배열자체로 주어야 하나? 마찬가지 배열자체로 deleteMany를 사용한 예제는 없었다.
  3. deleteMany는 ({기준 값:기준 value})를 통해 알맞는 값이 삭제되는 메서드이다 ({name: "안녕"})으로 name에 안녕이 포함된 값이 삭제되는 메서드이다.
  4. 그럼 _id를 주어야 하나 이것도 문제인게 나는 하나만 삭제할게 아닌 여러데이터를 삭제해야 해서 독립된 값은 불가능하다 _id에 값을 계속 넣어 줄 수 있는 것은 아니기 때문이다.
  5. 공통된 값이 중요하다. 그래서 생각한게 select이라는 속성을 만들고 default값은 false로 선택이 되면 patch로 select은 true가 되고 deleteMany({select: true})로 select이 true인 값만 삭제가 되게끔 구현하자.
  6. 클라이언트 측에서는 불리언값에 대한 state를 만들어 true false로 check uncheck를 핸들링
// 잘못된 코드 예시
const [toggleSelect, setToggleSelect] = useState(false)
toggleSelect === true ? checked(patch로 false) : unchecked(patch로 true)

check 모양에 tag와 uncheck 모양에 tag가 있고 patch로 불리언값을 바꾸게끔 핸들링 해주었다. 내 생각은 true가 된 값은 check가 되고 false가 된 값은 uncheck가 되고 서로 다른 모양에 check를 가지겠지 생각하였다.
그 결과, true가 되면 모두 체크가 되고 그리고 select은 true가 되버리고 반대 역시 마찬가지다.
다시 생각하였다 조건을 하나만 줄 것은 아니다. 불리언에 대한 state값은 map으로 돌린 그 값에 맞는 idx에 select과 비교가 필요하다.
하지만 이 방식또한 다를 것은 없었다.

다시 생각

일단 checked unchecked를 조건부 랜더링으로 딱딱 check 할게 아니고 check는 input type:checkbox의 특성 checkd를 통해 구현해놓고 나는 select만 생각하자

// 스타일드 컴포넌트를 사용한 checked이다
  &:checked {
    background: url("https://thedaju.cafe24.com/SkinImg/img/checkbox_on.svg")
      no-repeat center;
  }

불리언 state도 필요없다고 판단 내가 신경써야할 조건은 그 데이터에 맞는 idx에 select값

// 잘못된 코드
const idx = 데이터 idx에 select값을 가져온 코드
idx === false ? <unCheck patch(true)/> : idx === true ? <Check patch(fasle)/> : <unCheck patch(true)/>

위의 코드로 나는 true false가 자유로워지고 true true 연속 선택이 가능하다고 판단했지만 true false만 되거나 true true만 되거나 했다 이유는 마지막 조건 코드까지 갈 수가 없다 가기전에 이미 조건에 맞는 코드가 실행된다. 더군다나 true fasle 값에 맞게 check가 되지도 않는다.

해결

일단 true false에 자유를 해결해야 겠다고 생각.

  const handleSingleCheck = (
    checked: any,
    cartData: CartProudctType,
    _id: string
  ) => {
    if (checked) {
      setSelectDataArr((prev: any) => [...prev, cartData]);
      patchCartSelect(_id, true, "").then((data) => {
        setCartData(data.cart);
      });
    } else {
      setSelectDataArr(selectDataArr.filter((el) => el !== cartData));
      patchCartSelect(_id, false, "").then((data) => {
        setCartData(data.cart);
      });
    }
  };
retrun(
<Check>
<CheckIcon type="checkbox" onChange={(e) => handleSingleCheck(e.target.checked, cartData[index], _id)}
checked={cartData[index].select === true ? true : false} />
</Check>)

기존 잘못된 코드는 onClick으로 해결을 했는데 지금의코드는 onChange를 통해 e.target.checked를 통해 인덱스 각각에 checked 접근이 가능 접근한 idx checked되면 if(checked)가 실행 true인 patch가 실행된다. 그리고 그 접근한 checked는 어떤한 조건에 따라 true false가 가능했다. 그 전에는 준구난방에 체크아이콘이 되었다 데이터상에는 체크가 된건데 아이콘상에는 체크가 안되고 이런 문제가 있었고 이 문제는 cartData[index].select을 기준으로 true이면 check되고 fasle이면 uncheck 되게 하였다.

선택주문

앞에 선택삭제 부분에서 체크에 대한 부분을 해결해논 상태라 이 부분은 앞선 코드에 추가를 함으로써 기능을 추가했다.

첫 생각

  1. 전체주문 기능처럼 기존 productOrder에서 핸들링 하기로 생각 마찬가지 query를 넘겨주어 차별성을 두기로 생각.
  2. insertMany()를 사용할 것이고 넣어줄 데이터가 필요한데 이번에는 전체가 아닌 선택한 상품만 주문하는거니 선택한 상품들만 배열에 담아서 데이터에 넣어주어야 겠다
  3. cart에 남아있는 데이터는 deleteMany({select: true})로 삭제 왜냐 선택되면 배열에 담겨있는 동시에 select true이게끔 할거기 때문.
// server code
myorderRouter.post("/", async (req, res) => {
  try {
    if (!req.user) throw new Error("권환이 없습니다.");
    const { type } = req.query;
    const { selectData } = req.body;
    if (type === "선택상품주문") {
      const [] = await Promise.all([
        await Cart.deleteMany({ select: true }),
        await MyOrder.insertMany(selectData),
      ]);
      return res.json({
        message: "선택하신 상품의 주문이 완료되었습니다.",
      });
    }
  } catch (e) {
    res.status(400).json({ message: e.message });
  }
});

선택한 상품만 주문하는 코드만 일부 빼온 코드이다 req.query key값은 type value값은 선택상품주문으로 if 조건문에 코드만 실행되게끔 하였고, req.body에서 클라이언트가 보낸 배열에 담겨져있는 데이터를 가져와 insertMany로 데이터를 넣어주었다. 마지막 체크된 상품만 배열에 담겨져있을거고 체크가 되었다는거는 select이 true 일것이니 select: true인 값만 삭제시킨다.

  const [selectDataArr, setSelectDataArr] = useState<CartProudctType[]>([]);
  const handleSingleCheck = (
    checked: any,
    cartData: CartProudctType,
    _id: string
  ) => {
    if (checked) {
      setSelectDataArr((prev: any) => [...prev, cartData]);
      patchCartSelect(_id, true, "").then((data) => {
        setCartData(data.cart);
      });
    } else {
      setSelectDataArr(selectDataArr.filter((el) => el !== cartData));
      patchCartSelect(_id, false, "").then((data) => {
        setCartData(data.cart);
      });
    }
  };
retrun(
<Check>
<CheckIcon type="checkbox" onChange={(e) => handleSingleCheck(e.target.checked, cartData[index], _id)}
checked={cartData[index].select === true ? true : false} />
</Check>)

데이터를 담을 배열이 필요해 배열 state를 만들고 기존 아이콘을 체크할때의 함수에서 cartData인자를 추가해주었다. 이 cartData는 cartData[index]이다. onChange를 걸어준 부분을 보면 확인할 수 있다. 이제 checked가 되면 css로 걸어둔 checked와 select이 true가 되면 배열 state인 selectDataArr에 넣어준다. 나는 이 데이터가 담긴 배열 state를 서버로 넘겨주어 선택상품주문을 구현했다. 반대인 경우 else 부분을 살펴보면 unchecked가 되고 보내진 cartData[index]는 filter를 이용해 데이터가 빠졌다.

문제 (체크 아이콘이 selectpatch가 실행되어 데이터가 바뀌었는데도 안바뀌는 문제)

check 아이콘 부분에 문제가 있었다. 독립적으로 css만으로 껐다 켰다를 구현하였지만 원하는 방식은 select이 true 혹은 false 여부에따라 꺼졌다 켜졌다 이다. 실제 앞선 코드에서 checked={조건부 랜더링}을 통해 이 부분은 해결하였지만 바로바로 아이콘이 check 되게 보이지는 않았다 그렇다고 select이 안바뀐것은 아니다 리다이렉트를 시켜주면 아이콘이 check로 바뀐것을 확인할 수 있다. 강제로 리다이렉트를 시켜줄까 하였지만 강제로 리다이렉트는 아무래도 부담이 큰 작업이라고 생각이든다.

해결

생각해보면 쉽다 바뀐 select에 값을 가져오면 된다. 데이터는 바뀌었지만 클라이언트에서는 바뀐 데이터를 가져오지 않았다 당연히 아이콘은 안바뀐다. 애초에 cartData를 새로 다시 가져와 랜더링 하는 방법을 선택하였다. 여기서 느낀것은 cart에 가장큰 데이터를 차지하고있는 cartData state를 여기저기 prop을 뿌리니 헷갈렸다. 이거를 차라리 처음부터 redux로 관리하는것은 어땠을까 생각이든다. 하지만 맞는지는 솔직히 잘 모르겠다. 전역변수가 여기저기 뿌려져있는것 또한 안좋은 코드라고 알고있기 때문이다.

문제(체크 아이콘이 체크되면 총 결제금액이 두번랜더링되어 2배가 됨.) useEffect 두번 실행.


check 아이콘을 누르면 가격이 계속 뻥튀기가 된다. console 찍어본결과 [63000, 63000, 63000, 63000] 이렇게 계속 더해지고 있다.
patch 관련코드에 cartData를 수정하는 코드가 있다. 이부분이 실행되면서 useEffect에 map이 한번씩 실행되는 것 같다. 다시 정리해서 cartData의 갯수는 1개 그러면 totalArr의 갯수또한 1개여야 되는데 2, 3개 뻥튀가 되고 2개 2개 3개 3개 가 되어야하는데 각각 배수로 늘어났다. 그래서 애초에 useEffect를 끝내버리기로 생각했다. totalArr의 갯수는 cartData의 갯수와 똑같은거니깐 그리고 서로 배열에 담겨져있으니깐 length를 비교 같으면 끝내버렸다.

  const [totalArr, setTotalArr] = useState<number[]>([]);
  let totalPrice = 0;
  useEffect(() => {
    if (cartData.length === totalArr.length) return; // 해결한 부분
    cartData.map(({ price, quantity }) => {
      totalArr.push(price * quantity);
    });
  }, [cartData]);
  const totalPriceFunction = () => {
    for (let i = 0; i < totalArr.length; i++) {
      totalPrice += totalArr[i];
    }
    return totalPrice;
  };

전체선택

첫 생각

  1. 전체 cart 상품들의 select을 true로 바꿔주면 전체선택의 의미와 맞다 또한 체크아이콘도 정상 작동할 것이다.
  2. 사용자가 전체선택 후 선택주문을 할 수도 있으니 전체선택 상품들을 배열에 담을 필요가 있다.
  const handleAllCheck = () => {
    const idArray: CartProudctType[] = [];
    cartData.forEach((el: CartProudctType) => {
      idArray.push(el);
    });
    setSelectDataArr(idArray);
    patchCartSelect("", true, "?type=전체선택").then((data) => {
      setCartData(data.cart);
    });
  };

1개의 댓글

comment-user-thumbnail
2023년 7월 23일

좋은 글 감사합니다. 자주 올게요 :)

답글 달기