React.js 리덕스 비동기(http) boiler plate

강정우·2023년 1월 28일
0

react.js

목록 보기
34/45
post-thumbnail

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) => {
      	// fetch data 함수 선언부
        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
        }
        // fetchdata 실행부 => http request에 의한 오류처리 로직
        try {
            const cartData = await fetchData();
            // 카트 action을 dispatch할 때 undefined값이 뜬다면 빈 배열 처리 로직
            dispatch(cartActions.replaceCart({
                items: cartData.items || [],
                totalQuantity: cartData.totalQuantity
            }));
        } catch (error) {
            dispatch(uiActions.showNotification({
                status: "error",
                title: "Error!",
                message: "Send cart data failed!"
            }));
        }
    })
}

// POST하는 함수
export const sendCartData = (cart) => {
    return async (dispatch) => {
        // 1. ui로 사용자에게 현재 상태 알리기
        dispatch(
            uiActions.showNotification({
                status: "pending",
                title: "Sending...",
                message: "Sending cart data..."
            }));
        // 2. 실제 서버에 갔다 오는 함수를 선언 => 입력만 하기 때문에 여기서 추가 적인 .then()는 없음
        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...");
            };
        }
        // 3. 위에 async로 갔다온 data의 응답값을 토대로 다음 로직 실행
        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 분리
  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;
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글