저번 포스팅 [주문앱 4탄] 담기 기능 만들기(1)에 이어서 담기 기능을 완성해보려고 한다. 아래에 있는 기능들을 만들어보자!
위의 기능을 모두 구현한 결과는 아래와 같다!
현재까지 만든 장바구니는 같은 재료를 두번 담아도 아래처럼 단순하게 하나하나 쌓였다.
하지만 만약 실제 장바구니에 이렇게 재료가 담긴다면 사람들은 당황할 것이다. 사람들은 같은 재료를 여러번 담았을 때 장바구니에 중복되는 요소가 쌓이는 것이 아니라 기존의 요소에서 수량만 업데이트되는 것을 예상한다. 따라서 그렇게 만들어보기로 했다.
하위 컴포넌트에서 수량 선택 후 +담기
버튼을 누르면 최상위 컴포넌트 App에서 onSaveItem
함수가 호출되는데, 이부분을 아래처럼 작성해서 해결했다.
// 📃 App.jsx
const onSaveItem = selectedItemData => {
const newItemData = cartItems.concat(selectedItemData);
const mergedItemData = newItemData.reduce((acc, cur) => {
const found = acc.find((item) => item.id === cur.id);
if (found) {
found.amount += Number(cur.amount);
} else {
acc.push({...cur});
}
return acc;
}, []);
setCartItems(mergedItemData);
}
cartItems
에는 이전까지 담긴 재료들의 정보가 담겨있다. 여기에 새로 선택된 재료의 정보 selectedItemData
를 concat 함수로 합친 배열 newItemData
을 만든다.
이 newItemData
에 reduce 함수를 적용하여 중복을 제거하고 수량을 합쳐서 새로운 배열 mergedItemData
를 만든다.
중복을 제거하는 방법
새로 담은 재료의 id와 기존에 있던 재료들의 id를 비교하여 같은 요소를 find 함수로 찾는다. 찾았다면 기존의 수량에 새롭게 입력된 수량을 누적하고 요소 자체는 장바구니에 추가하지 않는다. 반대로 중복된 요소가 아니면 acc.push({...cur})
로 요소를 추가한다.
이렇게 하면 중복된 요소가 장바구니에 담기지 않으면서 기존 요소에 수량이 누적된다!
아래 사진을 보면 무가당(100g) 3개와 5개를 각각 담았을 때 목록이 두개(3개, 5개) 만들어지는 것이 아니라 한개만 만들어지고 수량이 8개로 누적되는 것을 알 수 있다.
장바구니에 이미 담은 재료의 수량을 변경할 수 있게 만들어보자. 아래의 사진에서 수량을 빼고 더하는 -
, +
버튼이 기능하게끔 해보려고 한다.
위의 -
, +
버튼을 누르면 각각 onRemove
와 onAdd
함수에 클릭된 재료의 id를 넘겼다.
// 📃 CartItem.jsx
const CartItem = ({ id }) => {
// ...생략
return (
// ...생략
<button onClick={() => onRemove(id)}>-</button>
<button onClick={() => onAdd(id)}>+</button>
);
};
export default CartItem;
onRemove
와 onAdd
함수는 최상위 컴포넌트인 App 컴포넌트에 있는데, 먼저 onAdd
함수를 만들어보았다.
onAdd
함수// 📃 App.jsx
const [cartItems, setCartItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);
const onAdd = (id) => {
const updatedArr = cartItems.map((cur) => {
if (cur.id === id) {
cur.amount++;
}
return cur;
});
const newTotalAmount = updatedArr.reduce((acc, cur) => {
return acc + (cur.amount * cur.price);
}, 0);
setTotalPrice(newTotalAmount)
setCartItems(updatedArr);
}
state인 cartItem
은 현재 장바구니에 담긴 재료 객체들이 모여있는 배열이다. totalPrice
는 장바구니 하단에 표시될 총액을 의미한다.
onAdd(id)에서 id는 장바구니에 담은 재료의 id를 의미한다.
cartItems
를 map으로 펼쳐 id를 이용해 +
버튼이 눌린 재료를 찾는다. 그리고 그 재료의 수량을 1 증가시킨다. 이렇게 담은 재료의 수량을 업데이트시킨 다음 updatedArr
라는 새로운 배열을 만들어준다.
const updatedArr = cartItems.map((cur) => {
if (cur.id === id) {
cur.amount++;
}
return cur;
});
이렇게 만든 updatedArr
에 reduce를 써서 담은 재료들의 총액을 구한다.
const newTotalAmount = updatedArr.reduce((acc, cur) => {
return acc + (cur.amount * cur.price);
}, 0);
처음에는 장바구니 안에 담긴 요소들을 의미하는 상태 cartItems
를 변경하는 것이 아니라 서버에서 가져오는 상태 backendData
를 바꾸려고 했다.
하지만 생각해보면 backendData
는 장바구니와는 관계 없이 메인 화면에 뿌려지는 데이터이다!! 따라서 이걸 바꾸는 것이 아니라 아예 장바구니에 있는 재료들을 뜻하는 상태 cartItems
를 추가하여 이것을 관리하게 되었다.
onRemove
함수onRemove
함수는 onAdd
함수와 대부분 동일하다. 다만 cur.amount++
을 cur.amount--
로 바꿔주면 된다.
장바구니 안에서 -
버튼을 누르면 onRemove
함수가 실행되며 수량이 하나씩 줄어든다. 만약 수량이 1인 상태에서 -
버튼을 누르면 수량이 0이 되는 것이 아니라 아예 해당 재료를 장바구니에서 삭제하려고 한다.
아래의 코드는 수량이 0이 될 때 재료가 삭제되는 기능을 포함한다. onAdd
함수와 두가지 부분에서 다른데, 이를 알아보자.
// 📃 App.jsx
const onRemove = (id) => {
const updatedArr = cartItems.map((cur) => {
if (cur.amount > 0 && cur.id === id) {
cur.amount--;
} else if (cur.amount === 0 && cur.id === id) {
cur.amount
}
return cur;
});
const newTotalAmount = updatedArr.reduce((acc, cur) => {
return acc + (cur.amount * cur.price);
}, 0);
setTotalPrice(newTotalAmount)
const removedArr = updatedArr.filter((cur) => {
return cur.amount > 0
})
setCartItems(removedArr);
}
map() 안의 내용이 일부 바뀐다. -
버튼을 누른다고 음수까지 수량이 줄어들면 안되기에 수량 감소는 수량이 0 초과일 때만 적용되도록 했다. 만약 수량이 0이면 변경하지 않고 0인채로 반환한다. 이제 updatedArr
에는 0부터 10까지의 수량을 가질 수 있는 요소가 들어있을 것이다.
const updatedArr = cartItems.map((cur) => {
if (cur.amount > 0 && cur.id === id) {
cur.amount--;
} else if (cur.amount === 0 && cur.id === id) {
cur.amount
}
return cur;
});
여기서 0을 수량으로 가지는 객체를 필터링하여 없애고 setCartItems
로 cartItems
을 removedArr
로 업데이트시켜준다. 이렇게 되면 결국 사용자 입장에서 보기에는 -
버튼을 누르다가 수량이 0 이하가 되면 장바구니에서 사라지는 것과 같이 보이게 된다.
const removedArr = updatedArr.filter((cur) => {
return cur.amount > 0
})
setCartItems(removedArr);
담기 기능을 모두 구현한 결과는 아래와 같다!
버그 고치기
1. 같은 재료를 3개, 4개 담으면 7개가 되어야 하는데 34개가 된다(어떤 때는 되고 어떤 때는 되지 않음) 👉 input의 value는 string 타입이어서 자동으로 string으로 변환됐던 것 같다. Number()를 이용해서 해결했다!
2. 장바구니에 담은 목록을 다 삭제해도 장바구니 버튼의 수량이 바로 바뀌지 않는다 👉 onSave 함수에만 버튼의 수량 상태를 업데이트하는 함수를 넣어서 버그가 발생했다. onAdd와 onRemove 함수에도 추가적으로 넣어서 해결했다.
context API
를 사용하여 상태관리가 가능하도록 리팩토링하고 싶다
reducer
를 사용해서 onAdd
, onRemove
함수를 리팩토링하고 싶다.
장바구니 안에 전체 삭제 버튼을 만들고 싶다.
그릭요거트 주문앱을 만들기로 했을 때 구현하려고 했던 기능을 오늘로 모두 구현했다!!
이전에 투두리스트를 만들 때 절대 못만들 것 같다고 생각했던 것 같은데, 이번에도 그랬다. 그런데 결과적으로 장바구니 기능을 구현해서 정말 기쁘다!
기존에 구상했던 기능은 모두 구현했지만, 아직 더 넣고 싶은 기능이 많다.
일단 useContext
, useReducer
Hook을 사용하여 리팩토링해보고 싶다. Firebase와 연동하는 것도 해보고 싶고, 로그인이나 결제 기능도 넣어보고 싶다. 모바일에서 예쁘게 볼 수 있게끔 반응형 디자인을 추가하고 싶고, 또 재료 설명에 이미지도 넣어서 카드 형식으로 만드는 것도 생각 중이다.
하나하나 추가하고 싶은 기능을 넣어보자!
그리고 이번 포스팅부터 블로그 포스팅 방식을 바꿨는데, 다른 사람이 볼 때도 티가 나는지 모르겠다! 이후에 공부 방식에 대한 포스팅에서 다뤄볼 예정이다👀
다음 포스팅에서는 리팩토링 혹은 Firebase 연동에 대해서 다루어보도록 하자.