본문 바로가기

React 실습

리덕스 툴킷과 함께 백앤드 서버에 데이터 보내기, 데이터 가져오기(3)

728x90

이제 까지 한 방법과 다른 방법은 작업 크리에이터를 사용하는 것이다.

이제 우리만의 작업 크리에이터를 만들 수 있고, 소위 썽크(thunk)를 만들 수 있다.

 

* thunk: 는 다른 함수가 완료될 때까지 기다리는 함수라고 볼 수 있다.

cart-slice.js 안에 이제까지 썻던 액션함수를 넣어서 쓸거임.

이전에 app.js에서 운용했던 모든 액션함수 fetch 관련 비동기 함수를 cart-slice.js에 모조리 넣을거임.

- 별도의 js함수에는 async/ await를 넣을 수 있기 때문에

- 오류 캐치는 try/ catch로..

- 함수로 만든 코드내 액션함수를 함수를 export하면서 이용하는 방식으로 사용할 것임.

import { createSlice } from '@reduxjs/toolkit';

import { uiActions } from './ui-slice';

const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    totalQuantity: 0,
  },
  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++;
      if (!existingItem) {
        state.items.push({
          id: newItem.id,
          price: newItem.price,
          quantity: 1,
          totalPrice: newItem.price,
          name: newItem.title,
        });
      } else {
        existingItem.quantity++;
        existingItem.totalPrice = existingItem.totalPrice + newItem.price;
      }
    },
    removeItemFromCart(state, action) {
      const id = action.payload;
      const existingItem = state.items.find((item) => item.id === id);
      state.totalQuantity--;
      if (existingItem.quantity === 1) {
        state.items = state.items.filter((item) => item.id !== id);
      } else {
        existingItem.quantity--;
      }
    },
  },
});

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(
        'https://react-http-6b4a6.firebaseio.com/cart.json',
        {
          method: 'PUT',
          body: JSON.stringify(cart),
        }
      );

      if (!response.ok) {
        throw new Error('Sending cart data failed.');
      }
    };

    try {
      await sendRequest();

      dispatch(
        uiActions.showNotification({
          status: 'success',
          title: 'Success!',
          message: 'Sent cart data successfully!',
        })
      );
    } catch (error) {
      dispatch(
        uiActions.showNotification({
          status: 'error',
          title: 'Error!',
          message: 'Sending cart data failed!',
        })
      );
    }
  };
};

export const cartActions = cartSlice.actions;

export default cartSlice;

 

* 디스패치를 이용할 때 항상 작업 크리에이터  였지만 cart-slice에서는 다른 함수를 반환하는 함수를 대신 전달한다.

리덕스 툴킷에 의해 그러한 준비가 되어있다.

유형 프로퍼티가 있는 작업 개체만 허용하는 것이 아니라 함수를 반환하는 작업 크리에이터도 허용한다.

 

import { Fragment, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';

import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import Products from './components/Shop/Products';
import Notification from './components/UI/Notification';
import { sendCartData } from './store/cart-slice';

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(() => {
    if (isInitial) {
      isInitial = false;
      return;
    }
    // 여기!!
    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;

* 이 컴포넌트는 이제 린 상태입니다. 여러 작업이 아닌 하나의 작업만 전달합니다. http 요청을 보내는 것을 신경 쓰지 않고 모든 힘든 작업은 리덕스 파일의 사용자 지정 작업 크리에이터 함수의 내부에서 발생함.

이와 같이 코드를 분할하는 것은 컴포넌트를 린 상대로 유지하기 때문에 좋은 것이라 할 수 있음.

 

 

* 데이터 가져오기

애플리케이션이 로드될 때 데이터를 가져오는 앱 작업 크리에이터를 빌드해보자

현재로서는 데이터만 보내고 있지만

애플리케이션이 로드될 때 데이터를 fetch 하지 않았기 때문에

다시 로드하면 모든 상태가 여전히 손실되어 있다. => 그렇게 되지 않게 데이터 firebase에서 가져올거임.

원리는 send하는거와 같습니다 다만 가져오고, 보낼때 http 요청이 다름으로 fetch할 때 해당 변수만 바꿔주면 되는것

import { uiActions } from './ui-slice';
import { cartActions } from './cart-slice';

export const fetchCartData = () => {
  return async (dispatch) => {
    const fetchData = async () => {
      const response = await fetch(
        'https://react-http-6b4a6.firebaseio.com/cart.json'
      );

      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: 'Fetching cart data failed!',
        })
      );
    }
  };
};

 

* 위의 코드를 사용할 app.js

 

import { Fragment, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';

import Cart from './components/Cart/Cart';
import Layout from './components/Layout/Layout';
import Products from './components/Shop/Products';
import Notification from './components/UI/Notification';
import { sendCartData, fetchCartData } from './store/cart-actions';

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;

 

* 지금까지 데이터를 가져오고 보내는 것을 리덕스를 이용해서 하는 법을 간단하게 흐름을 잡고 알아 봤다.

728x90