[Lululemon 클론 프로젝트] 장바구니 페이지
목표
상품 추가 / 삭제 / 수량 변경이 가능한 장바구니 페이지 구현
과정
1. 상품 데이터 받아오기
1) 사용자가 장바구니에 넣은 상품들의 목록을 렌더링하기위해 fetch 함수로 서버에서 데이터를 받아온 후 'itemList' 변수에 저장해두었다 .
2) 사용자를 식별한 다음 그에 맞는 데이터를 불러와야하기때문에 request header에 로그인시 발급했던 토큰 인가 과정을 추가했다.
코드🔻
const Bag = () => {
const [itemList, setItemList] = useState([]);
useEffect(() => {
fetch('http://10.58.3.71:8000/carts', {
method: 'GET',
headers: {
Authorization: localStorage.getItem('token'),
},
})
.then(res => res.json())
.then(data => {
setItemList(data.results);
});
}, []);
};
2. 상품 수량 변경하기
1) 원래는 lululemon의 사이트처럼 드랍다운 형식으로 수량 선택 버튼을 만드려고했지만 팀원분들과 이야기 후에 +, - 버튼으로 수량을 하나씩 조절할 수 있도록 구현했다.
2) 사용자가 장바구니 페이지에서 수량을 변경했을경우 DB의 데이터도 변경되어야하기때문에 fetch 함수와 PATCH 메서드를 사용했다.
코드🔻
const Item = ({ item, itemList, setItemList, onRemove }) => {
const { image, price, cart_id, quantity, name, color, size } = item;
const plusQuantity = () => {
fetch(`http://10.58.3.71:8000/carts/${cart_id}`, {
method: 'PATCH',
headers: {
Authorization: localStorage.getItem('token'),
},
body: JSON.stringify({
quantity: quantity + 1,
}),
}).then(res => {
if (res.ok) {
fetch('http://10.58.3.71:8000/carts', {
method: 'GET',
headers: {
Authorization: localStorage.getItem('token'),
},
})
.then(res => res.json())
.then(data => {
setItemList(data.results);
});
}
});
};
const minusQuantity = () => {
if (quantity === 1) {
alert('YOU CANNOT CHOOSE LESS THAN ONE');
} else {
fetch(`http://10.58.3.71:8000/carts/${cart_id}`, {
method: 'PATCH',
headers: {
Authorization: localStorage.getItem('token'),
},
body: JSON.stringify({
quantity: quantity - 1,
}),
}).then(res => {
if (res.ok) {
fetch('http://10.58.3.71:8000/carts', {
method: 'GET',
headers: {
Authorization: localStorage.getItem('token'),
},
})
.then(res => res.json())
.then(data => {
setItemList(data.results);
});
}
});
}
};
//(...)
return (
//(...)
<button onClick={minusQuantity}>-</button>
<div className="quantityInput">{quantity}</div>
<button onClick={plusQuantity}>+</button>
//(...)
);
};
3. 상품 합산 가격 계산하기
상품들의 총 가격 합계를 구하기 위해서 사용자가 장바구니에 담은 상품 각각의 수량과 가격을 곱한 다음 모든 상품들의 합산 가격을 다시 더하는 함수를 작성하려고했는데, 백엔드 팀에서 각 상품마다 그 상품의 가격과 구매 수량을 곱한 total_price 데이터를 만들어 보내주셔서 장바구니에 담긴 모든 상품들의 total_price 데이터를 더하는 함수로 간단하게 구현할 수 있었다.
코드🔻
const OrderSummary = ({ itemList }) => {
const getTotalPrice = itemList => {
let sum = 0;
for (let i = 0; i < itemList.length; i++) {
sum += itemList[0].total_price;
}
return sum;
};
return (
//(...)
<div className="summaryValue">
<p>$ {getTotalPrice(itemList)}</p>
<p>FREE</p>
<p>Calculated at checkout</p>
<p>USD $ {getTotalPrice(itemList)}</p>
</div>
//(...)
);
};
4. 상품 숨기기
lululemon 사이트에는 장바구니에 담긴 상품을 나중에 다시 구매할 수 있도록 일시적으로 숨겨주는 버튼이 있는데, 데이터를 아예 지워버리면 다시 불러오기가 힘들것같아 filter 메서드를 사용해 'save for later' 버튼을 누른 상품들을 제외하고 렌더링해주는 onRemove 함수를 작성해 구현했다.
코드🔻
const Bag = () => {
//(...)
const onRemove = useCallback(
id => {
setItemList(itemList.filter(item => item.cart_id !== id));
},
[itemList]
);
return (
//(...)
<Item
itemList={itemList}
setItemList={setItemList}
item={item}
key={item.cart_id}
onRemove={onRemove}
/>
//(...)
);
};
const Item = ({ item, itemList, setItemList, onRemove }) => {
//(...)
return (
//(...)
<div className="productFooter">
<p>Free Shipping + Free Returns</p>
<div className="footerBtns">
<button
onClick={() => {
onRemove(cart_id);
}}
>
Save for Later
</button>
//(...)
);
};
5. 상품 삭제하기
장바구니에서 상품을 삭제할 경우 DB에서도 지워질 수 있도록 fetch 함수와 DELETE 메서드를 사용해 구현했다.
코드🔻
const deleteItem = () => {
fetch(`http://10.58.3.71:8000/carts/${cart_id}`, {
method: 'DELETE',
headers: {
Authorization: localStorage.getItem('token'),
},
});
};
6. 빈 장바구니 페이지 렌더링하기
1) 장바구니 페이지에서 모든 상품을 삭제하면 구매를 유도하는 문구를 띄워주어야했는데, 조건부 렌더링을 활용해서 장바구니에 담긴 itemList의 길이가 0일때는 상품 목록과 결제정보를 숨기고 만들어둔 emptyBag 컴포넌트를 보여줄 수 있도록 구현했다.
2) itemList 데이터를 useEffect를 사용해서 받아왔는데, 이 경우 처음 mount가 될 때 useEffect가 실행되지 않아 useState의 초기값을 반환하기때문에 빈 배열이 들어와버려서 itemList의 길이에 따른 조건부 렌더링이 내 의도대로 되지 않았다. 이 에러는 itemList의 길이 조건을 설정해주는 부분에 itemList가 falsy값이 아닌 경우에만 렌더링이 실행될 수 있도록 && 연산자를 활용한 조건부 렌더링을 한번 더 사용해서 해결할 수 있었다.
코드🔻
const Bag = () => {
//(...)
return (
//(...)
{itemList && itemList.length !== 0 ? (
<div className="bagContainer">
<div className="leftContainer">
<h1>
My Bag <span>({itemList.length} Items)</span>
</h1>
{ itemList?.map(item => {
return (
<Item
itemList={itemList}
setItemList={setItemList}
item={item}
key={item.cart_id}
onRemove={onRemove}
/>
);
})}
</div>
<OrderSummary itemList={itemList} />
</div>
) : (
<EmptyBag goToMain={goToMain} />
)}
//(...)
);
};
시연 영상
회고
처음엔 어디서부터 시작을 해야할지 감조차 오지 않아서 장바구니 페이지를 구현하기에는 아직 내 실력이 많이 부족하다고 느껴졌고 자책도 많이 했었는데, 2주 후에 돌아보니 이 힘들었던 시간들 덕분에 오히려 내가 더 성장할 수 있었던 것 같다. 조건부 렌더링, fetch 함수 등 이전에는 개념으로만 공부했던 것들을 실제 웹페이지 제작에 활용해보면서 좀 더 확실히 이해할 수 있게 되었고, 막히는 부분이 생기면 스스로 공부도 하고 고민도 하다가 끝내 해결해냈던 모든 과정들이 앞으로 내가 더 나아갈 수 있도록 자신감을 심어주고 갔다. 사실 처음 해보는 프로젝트였던 만큼 개인 작업에서도, 협업에서도 서툰 부분들이 많았지만 늘 웃는 얼굴로 대해주셨던 팀원 분들께 정말 감사했다는 말씀을 드리고 싶다🥺