카트에 들어있는 상품들 가격 계산 & 카트에 들어있는 상품 지우기
import React, {useEffect, useState} from 'react'
import {useDispatch} from 'react-redux';
import {getCartItems, removeCartItem} from '../../../_actions/user_actions';
import UserCardBlock from './Sections/UserCardBlock';
function CartPage(props) {
const dispatch = useDispatch();
const [Total, setTotal] = useState(0);
useEffect(() => {
let cartItems = []
// 리덕스 user state에서 cart안에 상품이 들어있는지 확인
if(props.user.userData && props.user.userData.cart) {
// 카트 안에 상품이 하나 이상 들어있다면
if(props.user.userData.cart.length > 0){
props.user.userData.cart.forEach(item => {
cartItems.push(item.id)
})
// 밑에 액션에 매개변수로 방금 props로부터 받은 user의 userData에서 cart들을 담은 변수와,
// userData의 cart 정보도 주자. (이때 두번째 매개변수를 넣어주는 이유는 quantity는 user의 cart 부분에만 존재하기 때문)
dispatch(getCartItems(cartItems, props.user.userData.cart))
.then(response => {calculateTotal(response.payload)})
}
}
return () => {
}
}, [props.user.userData])
const calculateTotal = (cartDetail) => {
let total = 0;
cartDetail.map(item => {
total += parseInt(item.price) * item.quantity
})
setTotal(total);
}
let removeFromCart = (productId) => {
dispatch(removeCartItem(productId))
.then(response => {
calculateTotal(response.payload.productInfo)
})
}
return (
<div style={{ width: '85%', margin: '3rem auto'}}>
<h1>My Cart</h1>
{/* 밑에 props 주는 과정에서도
페이지가 렌더링 될 때는 return값을 먼저 렌더링되고 그 후에 props에 값이 들어올 때도 있다. 이때도 useEffect의
빈칸에 props.user.userData를 넣어주지 않으면 useEffect 안에 있는 조건문이 통과되지 않기 때문에 cartItems에는 아무것도 담기지 않는다.
UserCardBlock 컴포넌트에 props를 줄 때도 현재 컴포넌트 props에 값이 담겨있을 때를 조건으로 줘야 오류가 나지 않는다. */}
<div>
<UserCardBlock products={props.user.cartDetail && props.user.cartDetail} removeItem={removeFromCart}/>
</div>
<div style={{marginTop: '3rem'}}>
<h2>Total Amount: ${Total}</h2>
</div>
</div>
)
}
export default CartPage
CartPage.js
import React from 'react'
import "./UserCardBlock.css"
function UserCardBlock(props) {
const renderCartImage = (images) => {
if(images.length > 0) {
let image = images[0]
return `${process.env.REACT_APP_SERVER}/${image}`
}
}
const renderItems = () => (
props.products && props.products.map((product, index) => (
<tr key={index}>
<td>
<img style={{width:'70px'}} alt="product"
src={renderCartImage(product.images)} />
</td>
<td>
{product.quantity} EA
</td>
<td>
$ {product.price}
</td>
<td>
<button onClick={() => props.removeItem(product._id)}>
Remove
</button>
</td>
</tr>
))
)
return (
<div>
<table>
<thead>
<tr>
<th>Product Image</th>
<th>Product Quantity</th>
<th>Product Price</th>
<th>Remove From Cart</th>
</tr>
</thead>
<tbody>
{renderItems()}
</tbody>
</table>
</div>
)
}
export default UserCardBlock
UserCardBlock.js
export function removeCartItem(productId){
// 상세보기 페이지 만들 때 상품을 products_by_id 엔드포인트에서 갖고왔었다.
// 이때는 상품을 하나만 갖고와서 single로 type을 줬었지만
// 매개변수로 받은 cartItems에 여러 개의 cart id가 존재할 수 있기 때문에
// 이번엔 type을 array로 줘야한다.
const request = axios.get(`/api/users/removeFromCart?id=${productId}`)
.then(response => {
// productInfo, cart 정보를 조합해서 cartDetail를 만든다.
response.data.cart.forEach(item => {
response.data.productInfo.forEach((product,index) => {
if(item.id === product._id){
response.data.productInfo[index].quantity = item.quantity;
}
})
})
return response.data;
});
return {
type: REMOVE_CART_ITEM,
payload: request
}
}
user_actions.js
router.get("/removeFromCart", auth, (req,res) => {
// 먼저 cart 안에 내가 지우려고 한 상품을 지워주기
User.findOneAndUpdate(
{_id: req.user._id},
{
"$pull":
{"cart": {"id": req.query.id}}
},
{ new: true},
(err, userInfo) => {
// product collection에서 현재 남아있는 상품들의 정보를 가져오기
let cart = userInfo.cart;
let array = cart.map(item => {
return item.id
})
Product.find({_id: {$in: array}})
.populate('writer')
.exec((err, productInfo) => {
return res.status(200).json({
productInfo,
cart
})
})
}
)
})
server/routes/users.js
export const REMOVE_CART_ITEM = 'remove_cart_item';
types.js
import {
LOGIN_USER,
REGISTER_USER,
AUTH_USER,
LOGOUT_USER,
ADD_TO_CART,
GET_CART_ITEMS,
REMOVE_CART_ITEM,
} from '../_actions/types';
export default function(state={},action){
switch(action.type){
case REGISTER_USER:
return {...state, register: action.payload }
case LOGIN_USER:
return { ...state, loginSucces: action.payload }
case AUTH_USER:
return {...state, userData: action.payload }
case LOGOUT_USER:
return {...state }
case ADD_TO_CART:
return {...state,
userData: {
...state.userData,
cart: action.payload
}
}
case GET_CART_ITEMS:
return {...state, cartDetail: action.payload }
case REMOVE_CART_ITEM:
return {...state, cartDetail: action.payload.productInfo,
...state.userData, cart: action.payload.cart}
default:
return state;
}
}
user_reducer.js
router.get("/removeFromCart", auth, (req,res) => {
// 먼저 cart 안에 내가 지우려고 한 상품을 지워주기
User.findOneAndUpdate(
{_id: req.user._id},
{
"$pull":
{"cart": {"id": req.query.id}}
},
{ new: true},
(err, userInfo) => {
// product collection에서 현재 남아있는 상품들의 정보를 가져오기
let cart = userInfo.cart;
let array = cart.map(item => {
return item.id
})
Product.find({_id: {$in: array}})
.populate('writer')
.exec((err, productInfo) => {
return res.status(200).json({
productInfo,
cart
})
})
}
)
})
users.js
상품들 가격 계산하는 과정은 쉬워서 굳이 설명을 안해놔도 될 것 같다.
위 과정은 다음과 같다
1. UserCardBlock 컴포넌트에서 Remove 버튼을 눌렀을 때 부모 컴포넌트인 CartPage에서 props로 준 removeItem에 매개변수로 해당 상품의 id값이 들어가면서 호출된다.
2. CartPage에선 매개변수로 받은 상품 id를 removeCartItem 액션에 매개변수로 넣어주면서 바로 호출한다.
3. user_actions.js에서 removeCartItem 액션이 query로 매개변수로 받은 상품 id값을 주면서 users.js의 removeFromCart 라우터에 해당 상품을 제거하도록 요청한다.
4. users.js의 removeFromCart 라우터에선 $pull 문법을 이용하여 cart로부터 쿼리로 받은 상품 id값과 일치하는 상품을 제거한다.
그리고 전에서 나왔듯이 find()에 여러개의 상품을 찾게되는 경우가 있기 때문에 $in 문법을 사용한다.
쿼리로 받은 상품 id의 상품을 제거한 후의 cart를 map으로 하나 하나 뽑아서 array에 배열 형태로 만들어서 넣어준다.
이것을 $in 에 넣어서 모든 상품을 찾은 productInfo와 user의 cart를 client에 보낸다.
5. 다시 user_actions.js에선 4번에서 받은 productInfo와 cart를 이용하여 이중 반복문으로 각각 탐색한다.
탐색하는 과정에서 cart의 id값과 productInfo의 id값이 같다면 response.data.product[index].quantity = item.quantity를 통해
quantity 필드를 만들어 줌과 동시에 cart 모델만 갖고 있던 quantity값을 넣어준다. 이 response.data를 payload로 return한다.
6. user_action까지 다 마친 후 user_reducer에선 REMOVE_CART_ITEM에 해당하는 케이스를 처리한다.
이때 기존에 있던 state와 userData는 그대로 넣어주고 위에서 quantity 필드를 만들어주면서 값까지 넣어줬던 response.data.product를 cartDetail에 넣어주고, cart는 유저의 cart를 넣어준다.
이렇게 되면 redux-dev tools에선 state, userData, cartDetail, cart 목록이 있을 것이다.