2-2. Action creators (lean code)
- 역시 리액트는 모든 컴포넌트와 코드들이 lean하게 짧고 간결한 코드를 유지하여야 한다. 그래서 cart-slice.js에 들어있는 async 함수(action creators)와 기존 slice 를 분리하여 관리하주면 좋다.
1. 기존 slice.js
import { createSlice } from "@reduxjs/toolkit";
const cartSlice = createSlice({
name: "cart",
initialState: {
items: [],
totalQuantity: 0,
changed: false,
},
reducers: {
replaceCart(state, action) {
state.totalQuantity = action.payload.totalQuantity;
state.items = action.payload.items;
},
addItemToCart(state, action) {
const newItem = action.payload;
const existingItem = state.items.find((item) => item.id === newItem.id);
state.totalQuantity++;
state.changed = true;
if (!existingItem) {
state.items.push({
id: newItem.id,
price: newItem.price,
quantity: 1,
totalPrice: newItem.price,
name: newItem.title,
});
} else {
existingItem.quantity++;
existingItem.totalPrice += newItem.price;
}
},
removeItemFromCart(state, action) {
const id = action.payload;
const existingItem = state.items.find((item) => item.id === id);
state.totalQuantity--;
state.changed = true;
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;
- createSlice에는 항상 3가지 prop이 존재하고 그것들을 채워줘야한다.
- reducers에 들어있는 리덕스에 의해 자동으로 생성되는 action creators의 props들중 action에는 payload state가 자동으로 생성되어 들어가있다. 그래서 payload prop에 접근하여 값을 얻어오면 된다.
2. 추가된 action.js
import { cartActions } from "./cart-sclice";
import { uiActions } from "./ui-slice";
export const fetchCartData = () => {
return (async (dispatch) => {
const fetchData = async () => {
const response = await fetch("라우터 URL");
if (!response.ok) {
throw new Error("Could not fetch cart data");
}
const data = await response.json();
return data
}
try {
const cartData = await fetchData();
dispatch(cartActions.replaceCart({
items: cartData.items || [],
totalQuantity: cartData.totalQuantity
}));
} catch (error) {
dispatch(uiActions.showNotification({
status: "error",
title: "Error!",
message: "Send cart data failed!"
}));
}
})
}
export const sendCartData = (cart) => {
return async (dispatch) => {
dispatch(
uiActions.showNotification({
status: "pending",
title: "Sending...",
message: "Sending cart data..."
}));
const sendRequest = async () => {
const response = await fetch("라우터 URL", {
method: "PUT", body: JSON.stringify({ items: cart.items, totalQuantity: cart.totalQuantity }),
});
if (!response.ok) {
throw new Error("Something is go wrong...");
};
}
try {
await sendRequest();
dispatch(uiActions.showNotification({
status: "success",
title: "Success!",
message: "Send cart data successfully!"
}));
} catch (error) {
dispatch(uiActions.showNotification({
status: "error",
title: "Error!",
message: "Send cart data failed!"
}));
}
};
};
- 각 함수에 매개변수로 들어있는 "dispatch"는 리덕스에서 자동으로 dispatch 처리해준다.
3. 사용할 컴포넌트
import ...
let isInitial = true;
function App() {
const dispatch = useDispatch();
const showCart = useSelector((state) => state.ui.cartIsVisible);
const cart = useSelector(state => state.cart);
const notification = useSelector(state => state.ui.notification);
useEffect(() => {
dispatch(fetchCartData());
}, [dispatch]);
useEffect(() => {
if (isInitial) {
isInitial = false;
return;
}
if (cart.changed) {
dispatch(sendCartData(cart));
}
}, [cart, dispatch]);
return (
<Fragment>
{notification && <Notification status={notification.status} title={notification.title} message={notification.message} />}
<Layout>
{showCart && <Cart />}
<Products />
</Layout>
</Fragment>
);
}
export default App;
추가. UI 컴포넌트
import classes from './Notification.module.css';
const Notification = (props) => {
let specialClasses = '';
if (props.status === 'error') {
specialClasses = classes.error;
}
if (props.status === 'success') {
specialClasses = classes.success;
}
const cssClasses = `${classes.notification} ${specialClasses}`;
return (
<section className={cssClasses}>
<h2>{props.title}</h2>
<p>{props.message}</p>
</section>
);
};
export default Notification;