리덕스 스토어 생성
import { configureStore } from "@reduxjs/toolkit";
import uiReducer from './ui-slice';
import cartReducer from './cart-slice';
const store = configureStore({
reducer: {
ui: uiReducer,
cart: cartReducer
}
})
export default store;
cart-slice 생성 (리듀서 업데이트)
- 초기 상태 정의 (initialState)
- 슬라이스 정의 (createSlice)
- 액션 내보내기 (cartActions)
- 상태관리할 컴포넌트에서 dispatch를 사용하여 액션을 보낼 수 있음
- 리듀서 내보내기 (default export)
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
cartItems: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addCartItem(state, action) {
const newCartItem = action.payload;
const existingItem = state.cartItems.find(item => item.id === newCartItem.id);
if (!existingItem) {
state.cartItems.push(newCartItem);
} else {
existingItem.quantity++;
existingItem.total += newCartItem.price;
}
state.totalQuantity++;
},
removeCartItem(state, action) {
const id = action.payload;
const existingItem = state.cartItems.find(item => item.id === id);
if (existingItem.quantity === 1) {
state.cartItems = state.cartItems.filter(item => item.id !== id);
} else {
existingItem.quantity--;
existingItem.total -= existingItem.price;
}
state.totalQuantity--;
}
}
});
export const cartActions = cartSlice.actions;
export default cartSlice.reducer;
카트 버튼 조건부 렌더링
ui-slice 생성
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
cartIsVisible: false,
};
const uiSlice = createSlice({
name: 'ui',
initialState,
reducers: {
toggle(state) {
state.cartIsVisible = !state.cartIsVisible;
},
}
});
export const uiActions = uiSlice.actions;
export default uiSlice.reducer;
- Cart 조건부 렌더링을 위해 toggle핸들러 CartButton에 걸어놓음
import React from "react";
import styles from './CartButton.module.css';
import { uiActions } from "../../store/ui-slice";
import { useDispatch } from "react-redux";
const CartButton = () => {
const dispatch = useDispatch();
const toggleCartHandler = e => {
dispatch(uiActions.toggle());
};
return (
<button className={styles.button} onClick={toggleCartHandler}>
<span>My Cart</span>
<span className={styles.badge}>0</span>
</button>
);
};
export default CartButton;
App.js
- ui-slice의 cartIsVisible 상태를 useSelector로 불러와서 Cart를 조건부 렌더링
import React from 'react';
import Layout from './redux-cart/components/Layout/Layout';
import Cart from './redux-cart/components/Cart/Cart';
import Products from './redux-cart/components/Shop/Products';
import { useSelector } from 'react-redux';
const App = () => {
const isVisible = useSelector(state => state.ui.cartIsVisible);
return (
<Layout>
{isVisible && <Cart />}
<Products />
</Layout>
);
};
export default App;
카트에 상품 추가 상태관리
Cart.js
- Redux store에서 장바구니 항목들을 가져와 화면에 표시
- cartItems 배열을 useSelector 훅을 사용하여 가져오고, 각 항목을 CartItem 컴포넌트로 렌더링
import React from "react";
import Card from '../UI/Card';
import styles from './Cart.module.css';
import CartItem from './CartItem';
import { useSelector } from 'react-redux'
const Cart = () => {
const cartItems = useSelector(state => state.cart.cartItems);
console.log(cartItems)
return (
<Card className={styles.cart}>
<h2>Your Shopping Cart</h2>
<ul>
{cartItems.map(item => <CartItem key={item.id} item={item}/>)}
</ul>
</Card>
);
};
export default Cart;
CartItem.js
- 장바구니에 담긴 개별 상품의 정보를 화면에 표시
- "+" 버튼을 클릭하면 addCartHandler 함수가 실행되어 해당 상품의 수량을 증가
- "-" 버튼을 클릭하면 removeCartHandler 함수가 실행되어 해당 상품의 수량을 감소
import React from "react";
import styles from './CartItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";
const CartItem = ({item}) => {
const dispatch = useDispatch();
const { title, quantity, total, price, id } = item;
const addCartHandler = e => {
dispatch(cartActions.addCartItem(item));
};
const removeCartHandler = e => {
dispatch(cartActions.removeCartItem(id));
};
return (
<li className={styles.item}>
<header>
<h3>{title}</h3>
<div className={styles.price}>u
{total}{' '}
<span className={styles.itemprice}>({price}/item)</span>
</div>
</header>
<div className={styles.details}>
<div className={styles.quantity}>
x <span>{quantity}</span>
</div>
<div className={styles.actions}>
<button onClick={removeCartHandler}>-</button>
<button onClick={addCartHandler}>+</button>
</div>
</div>
</li>
);
};
export default CartItem;
ProductItem.js
- Add to Cart 버튼을 클릭하면 addCartHandler 함수가 실행되어 Redux store에 상품을 추가되고 화면에 표시
import React from "react";
import Card from '../UI/Card';
import styles from './ProductItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";
const ProductItem = ({description, id, price, title}) => {
const dispatch = useDispatch();
const addCartHandler = e => {
dispatch(cartActions.addCartItem({
id,
title,
quantity: 1,
price,
total: price
}));
};
return (
<li className={styles.item}>
<Card>
<header>
<h3>{title}</h3>
<div className={styles.price}>{price}</div>
</header>
<p>{description}</p>
<div className={styles.actions}>
<button onClick={addCartHandler}>Add to Cart</button>
</div>
</Card>
</li>
);
};
export default ProductItem;
카트에 상품 제거 상태관리
- 토탈 개수 상태관리위해 totalQuantity 가져옴
import React from "react";
import styles from './CartButton.module.css';
import { uiActions } from "../../store/ui-slice";
import { useDispatch, useSelector } from 'react-redux';
const CartButton = () => {
const totalQuantity = useSelector(state => state.cart.totalQuantity);
const dispatch = useDispatch();
const toggleCartHandler = e => {
dispatch(uiActions.toggle());
};
return (
<button className={styles.button} onClick={toggleCartHandler}>
<span>My Cart</span>
<span className={styles.badge}>{totalQuantity}</span>
</button>
);
};
export default CartButton;
CartItem.js
import React from "react";
import styles from './CartItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";
const CartItem = ({item}) => {
const dispatch = useDispatch();
const { title, quantity, total, price, id } = item;
const addCartHandler = e => {
dispatch(cartActions.addCartItem(item));
};
const removeCartHandler = e => {
dispatch(cartActions.removeCartItem(id));
};
return (
<li className={styles.item}>
<header>
<h3>{title}</h3>
<div className={styles.price}>u
{total}{' '}
<span className={styles.itemprice}>({price}/item)</span>
</div>
</header>
<div className={styles.details}>
<div className={styles.quantity}>
x <span>{quantity}</span>
</div>
<div className={styles.actions}>
<button onClick={removeCartHandler}>-</button>
<button onClick={addCartHandler}>+</button>
</div>
</div>
</li>
);
};
export default CartItem;
cart-slice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
cartItems: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addCartItem(state, action) {
const newCartItem = action.payload;
const existingItem = state.cartItems.find(item => item.id === newCartItem.id);
if (!existingItem) {
state.cartItems.push(newCartItem);
} else {
existingItem.quantity++;
existingItem.total += newCartItem.price;
}
state.totalQuantity++;
},
removeCartItem(state, action) {
const id = action.payload;
const existingItem = state.cartItems.find(item => item.id === id);
if (existingItem.quantity === 1) {
state.cartItems = state.cartItems.filter(item => item.id !== id);
} else {
existingItem.quantity--;
existingItem.total -= existingItem.price;
}
state.totalQuantity--;
}
}
});
export const cartActions = cartSlice.actions;
export default cartSlice.reducer;