useEffect
를 사용import { configureStore } from "@reduxjs/toolkit";
import uiSlice from "./ui-slice";
import cartSlice from "./cart-slice";
const store = configureStore({
reducer: {
ui: uiSlice.reducer,
cart: cartSlice.reducer,
},
});
export default store;
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;
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import store from "./store";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
// components/CartButton.jsx
import classes from "./CartButton.module.css";
import { useDispatch } from "react-redux";
import { uiActions } from "../../store/ui-slice";
const CartButton = (props) => {
const dispatch = useDispatch();
const toggleCartHandler = () => {
dispatch(uiActions.toggle());
};
return (
<button className={classes.button} onClick={toggleCartHandler}>
<span>My Cart</span>
<span className={classes.badge}>1</span>
</button>
);
};
export default CartButton;
// App.js
import Cart from "./components/Cart/Cart";
import Layout from "./components/Layout/Layout";
import Products from "./components/Shop/Products";
import { useSelector } from "react-redux";
function App() {
const isVisibleCart = useSelector((state) => state.ui.cartIsVisible);
return (
<Layout>
{isVisibleCart && <Cart />}
<Products />
</Layout>
);
}
export default App;
{
id,
price,
totalPrice,
name
}
// store/cart-slice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
items: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
addItemToCart(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
if (!existingItem) {
state.items.push({
itemId: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.name,
});
} else {
existingItem.quantity++;
existingItem.totalPrice += newItem.price;
}
},
removeItemFromCart(state, action) {
const id = action.payload;
const existingItem = state.item.find((item) => item.id === id);
if (existingItem.quantity === 1) {
state.items = state.items.filter((item) => item.id !== id);
} else {
existingItem.quantity--;
existingItem.totalPrice -= existingItem.price;
}
},
},
});
export const cartActions = cartSlice.actions;
export default cartSlice;
### Action Dispatch
- 컴포넌트 수정 (Products.jsx)
``` jsx
import ProductItem from "./ProductItem";
import classes from "./Products.module.css";
const DUMMY_PRODUCTS = [
{ id: "p1", price: 6, title: "book1", description: "english book" },
{ id: "p2", price: 5, title: "book2", description: "korean book" },
];
const Products = (props) => {
return (
<section className={classes.products}>
<h2>Buy your favorite products</h2>
<ul>
{DUMMY_PRODUCTS.map((product) => (
<ProductItem
key={product.id}
title={product.title}
price={product.price}
description={product.description}
/>
))}
</ul>
</section>
);
};
export default Products;
// ProductItem.jsx
import Card from "../UI/Card";
import classes from "./ProductItem.module.css";
import { useDispatch } from "react-redux";
import { cartActions } from "../../store/cart-slice";
const ProductItem = (props) => {
const { title, price, description, id } = props;
const dispatch = useDispatch();
const addToCartHandler = () => {
dispatch(
cartActions.addItemToCart({
id,
title,
price,
})
);
};
return (
<li className={classes.item}>
<Card>
<header>
<h3>{title}</h3>
<div className={classes.price}>${price.toFixed(2)}</div>
</header>
<p>{description}</p>
<div className={classes.actions}>
<button onClick={addToCartHandler}>Add to Cart</button>
</div>
</Card>
</li>
);
};
export default ProductItem;
// cart-slice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
items: [],
totalQuantity: 0,
};
const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
addItemToCart(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
state.totalQuantity++;
if (!existingItem) {
state.items.push({
itemId: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.name,
});
} else {
existingItem.quantity++;
existingItem.totalPrice += newItem.price;
}
},
removeItemFromCart(state, action) {
const id = action.payload;
const existingItem = state.item.find((item) => item.id === id);
state.totalQuantity--;
if (existingItem.quantity === 1) {
state.items = state.items.filter((item) => item.id !== id);
} else {
existingItem.quantity--;
existingItem.totalPrice -= existingItem.price;
}
},
},
});
export const cartActions = cartSlice.actions;
export default cartSlice;
// CartButton.jsx
import classes from "./CartButton.module.css";
import { useDispatch, useSelctor } from "react-redux";
import { uiActions } from "../../store/ui-slice";
const CartButton = (props) => {
const dispatch = useDispatch();
const cartQuantity = useSelctor((state) => state.cart.totalQuantity);
const toggleCartHandler = () => {
dispatch(uiActions.toggle());
};
return (
<button className={classes.button} onClick={toggleCartHandler}>
<span>My Cart</span>
<span className={classes.badge}>{cartQuantity}</span>
</button>
);
};
export default CartButton;
// Cart.jsx
import Card from "../UI/Card";
import classes from "./Cart.module.css";
import CartItem from "./CartItem";
import { useSelector } from "react-redux";
const Cart = (props) => {
const cartList = useSelector((state) => state.cart.items);
return (
<Card className={classes.cart}>
<h2>Your Shopping Cart</h2>
<ul>
{cartList.map((cartItem) => (
<CartItem
key={cartItem.id}
item={{
title: cartItem.title,
quantity: cartItem.quantity,
total: cartItem.totalPrice,
price: cartItem.price,
}}
/>
))}
</ul>
</Card>
);
};
export default Cart;
import classes from "./CartItem.module.css";
import { useDispatch } from "react-redux";
import { cartActions } from "../../store/cart-slice";
const CartItem = (props) => {
const { title, quantity, total, price, id } = props.item;
const dispatch = useDispatch();
const removeItemHandler = () => {
dispatch(
cartActions.removeItemFromCart({
id,
title,
price,
})
);
};
return (
<li className={classes.item}>
<header>
<h3>{title}</h3>
<div className={classes.price}>
${total.toFixed(2)}{" "}
<span className={classes.itemprice}>(${price.toFixed(2)}/item)</span>
</div>
</header>
<div className={classes.details}>
<div className={classes.quantity}>
x <span>{quantity}</span>
</div>
<div className={classes.actions}>
<button onClick={removeItemHandler}>-</button>
<button>+</button>
</div>
</div>
</li>
);
};
export default CartItem;
// App.js
import Cart from "./components/Cart/Cart";
import Layout from "./components/Layout/Layout";
import Products from "./components/Shop/Products";
import { useSelector } from "react-redux";
import { useEffect } from "react";
function App() {
const isVisibleCart = useSelector((state) => state.ui.cartIsVisible);
const cart = useSelector((state) => state.cart);
useEffect(() => {
fetch("backendURl", {
method: "PUT",
body: JSON.stringify(cart);
});
}, [cart]);
return (
<Layout>
{isVisibleCart && <Cart />}
<Products />
</Layout>
);
}
export default App;
위 코드대로라면, useEffect는 초기에 실행되기 때문에
cart가 빈 값으로 덮어쓰기 될 수 있음.
import Cart from "./components/Cart/Cart";
import Layout from "./components/Layout/Layout";
import Products from "./components/Shop/Products";
import { useSelector } from "react-redux";
import { useEffect } from "react";
function App() {
const isVisibleCart = useSelector((state) => state.ui.cartIsVisible);
const cart = useSelector((state) => state.cart);
useEffect(() => {
const sendCartData = async () => {
await response = fetch("backendURl", {
method: "PUT",
body: JSON.stringify(cart);
});
if (!response.ok) {
throw new Error('Send Data Failed')
}
const responseData = await response.json();
};
}, [cart]);
return (
<Layout>
{isVisibleCart && <Cart />}
<Products />
</Layout>
);
}
export default App;
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
cartIsVisible: false,
notification: null,
};
const uiSlice = createSlice({
name: "ui",
initialState,
reducers: {
toggle(state) {
state.cartIsVisible = !state.cartIsVisible;
},
showNotification(state, action) {
state.notification = {
status: action.payload.status,
title: action.payload.title,
message: action.payload.message,
};
},
},
});
export const uiActions = uiSlice.actions;
export default uiSlice;
import Cart from "./components/Cart/Cart";
import Layout from "./components/Layout/Layout";
import Products from "./components/Shop/Products";
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { uiActions } from './store/ui-slice';
function App() {
const dispatch = useDispatch();
const isVisibleCart = useSelector((state) => state.ui.cartIsVisible);
const cart = useSelector((state) => state.cart);
useEffect(() => {
const sendCartData = async () => {
dispatch(uiActions.showNotification({
status: 'pending',
title; 'Sending ...',
message: 'Sending Cart Data'
}));
await response = fetch("backendURl", {
method: "PUT",
body: JSON.stringify(cart);
});
if (!response.ok) {
dispatch(uiActions.showNotification({
status: 'error',
title; 'Error',
message: 'Failed Sending Cart Data'
}));
}
const responseData = await response.json();
dispatch(uiActions.showNotification({
status: 'Success',
title; 'Success',
message: 'Success Sending Cart Data'
}));
};
sendCartData().catch(error => {
dispatch(uiActions.showNotification({
status: 'error',
title; 'Error',
message: 'Failed Sending Cart Data'
}));
})
}, [cart, dispatch]);
return (
<Layout>
{isVisibleCart && <Cart />}
<Products />
</Layout>
);
}
export default App;
App.js 수정
import Cart from "./components/Cart/Cart";
import Layout from "./components/Layout/Layout";
import Products from "./components/Shop/Products";
import { useSelector, useDispatch } from "react-redux";
import { useEffect } from "react";
import { uiActions } from './store/ui-slice';
let isInitial = true;
function App() {
const dispatch = useDispatch();
const isVisibleCart = useSelector((state) => state.ui.cartIsVisible);
const cart = useSelector((state) => state.cart);
useEffect(() => {
const sendCartData = async () => {
dispatch(uiActions.showNotification({
status: 'pending',
title; 'Sending ...',
message: 'Sending Cart Data'
}));
await response = fetch("backendURl", {
method: "PUT",
body: JSON.stringify(cart);
});
if (!response.ok) {
dispatch(uiActions.showNotification({
status: 'error',
title; 'Error',
message: 'Failed Sending Cart Data'
}));
}
const responseData = await response.json();
dispatch(uiActions.showNotification({
status: 'Success',
title; 'Success',
message: 'Success Sending Cart Data'
}));
};
if (isInitial) {
isInitial = false;
return;
}
sendCartData().catch(error => {
dispatch(uiActions.showNotification({
status: 'error',
title; 'Error',
message: 'Failed Sending Cart Data'
}));
})
}, [cart]);
return (
<Layout>
{isVisibleCart && <Cart />}
<Products />
</Layout>
);
}
export default App;
uiActions.showNotification
과 같은 메서드.// store/cart-slice.js
const sendCartData = (cartData) => {
return (dispatch) => {
dispatch(
uiActions.showNotification({
status: 'pending',
title: 'Sending...',
message: 'Sending cart data's
})
)
}
const sendCartData = async () => {
dispatch(uiActions.showNotification({
status: 'pending',
title; 'Sending ...',
message: 'Sending Cart Data'
}));
await response = fetch("backendURl", {
method: "PUT",
body: JSON.stringify(cart);
});
if (!response.ok) {
dispatch(uiActions.showNotification({
status: 'error',
title; 'Error',
message: 'Failed Sending Cart Data'
}));
}
const responseData = await response.json();
dispatch(uiActions.showNotification({
status: 'Success',
title; 'Success',
message: 'Success Sending Cart Data'
}));
};
await sendCartData();
};
$ npm install @redux-devtools/extension
const store = createStore(
reducer,
composeWithDevTools(
applyMiddleware(...middleware),
// other store enhancers if any
),
);