react/redux 프로젝트 만들기

정영찬·2022년 3월 29일
0

프로젝트 실습

목록 보기
3/60
post-thumbnail

Redux installation ans setup

프로젝트를 하나 생성하고, react-router-dom, axios, react react-router 패키지 설치

React/Redux Project Structure

프로젝트내부에 디렉토리를 생성하여 구조를 조정한다.

src 내부에 폴더와 파일을 생성한다.

containers: 컴포넌트들이 들어갈 폴더
redux : redux 관련 폴더와 파일들이 들어간다.
- actions : 선언한 Actions파일이 들어간다.
- constants: action-type파일이 들어간다.
reducers : reducer 관련 파일이 들어간다.

Create Reduce Actions Types

constants폴더 내부에 action-types.js파일을 생성하고 액션 유형을 작성할 것이다.

export const ActionTypes = {
   SET_PRODUCTS : "SET_PRODUCTS",
   SELECTED_PRODUCT: "SELECTED_PRODUCT",
   REMOVESELECTED_PRODUCT: "REMOVE_SELECTED_PRODUCT",
}

Create Redux Actions

import { ActionTypes } from "../constants/action-types"
export const setProducts = (products) => {
    return {
        type:ActionTypes.SET_PRODUCTS,
        payload: products,

    }
};


export const selectedProduct = (product) => {
    return {
        type: ActionTypes.SELECTED_PRODUCT,
        payload: product,
    };
};

Create Reducers

productReducer.js

import { ActionTypes } from "../constants/action-types";

const initialState = {
    products: [{
        id : 1,
        title:"jyc",
        category:"human",
        },
    ],
}
export const productReducer = (state, {type, payload) => {
    switch(type){
        case ActionTypes.SET_PRODUCTS:
            return state;
        default:
            break;
    }
}

src/reducers/index.js

import { combineReducers } from "redux";
import { productReducer } from "./productReducer";

const reducers = combineReducers({
    allProducts: productReducer,
    
})

export default reducers

Create Redux Store

store.js

import { createStore } from "redux";
import reducers from "./reducers/index";

const store = createStore(
  reducers,
  {},
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSTION__()
);

export default store;

Connect React with Redux

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './redux/store';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

Create React Components

Header : 헤더

import React from 'react';

const Header = () => {
    return ( 
        <div className="ui fixed menu">
            <div className='ui container center'>
                <h2>WhateverShop</h2>
            </div>
        </div>
    )
}

export default Header

ProductComponents : 제품

import React from 'react';

const ProductComponent = () => {
    return(
    <div>
        <h1>ProductComponent</h1>
    </div>
    )
}

export default ProductComponent

ProductDetail : 제품목록 클릭시 나타나는 상세정보

import React from 'react';

const ProductDetail = () => {
    return(
    <div>
        <h1>ProductDetail</h1>
    </div>
    )
}

export default ProductDetail

ProductListing : 제품 목록

import React from 'react';

const ProductListing = () => {
    return(
    <div>
        <h1>ProductListing</h1>
    </div>
    )
}

export default ProductListing

일단 간단하게만 제작하고 라우팅을 추가한 다음 상세하게 작성한다.

Add Routing to Projects

app.js

import "./App.css";
import Header from "./containers/Header";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import ProductListing from "./containers/ProductListing";
import ProductDetail from "./containers/ProductDetail";

function App() {
  return (
    <div className="App">
      <Router>
        <Header />
        <Routes>
          <Route path="/" exact component={ProductListing} />
          <Route path="/product/:productId" exact component={ProductDetail} />
          <Route>404 Not Found!</Route>
        </Routes>
      </Router>
    </div>
  );
}

export default App;

UseSelector to access state

ProductComponent.js

import React from 'react';
import { useSelector } from 'react-redux';

const ProductComponent = () => {
    const products = useSelector((state) => state.allProducts.products);
    const {id, title} = products[0];
    return(
    <div className="four column wide">
        <h1 className='ui link cards'>
            <div className='card'>
                <div className='image'></div>
                <div className='content'>
                    <div className='header'>{title}</div>
                </div>
            </div>
        </h1>
    </div>
    )
}

export default ProductComponent

Use Axios for Redux Api Call

fask stor api를 사용해서 물건의 정보를 가져올 것이다.

해당 코드를 사용해서 데이터를 가져온다.

ProductListing.js

import React ,{useEffect} from 'react';
import { useSelector } from 'react-redux';
import ProductComponent from './ProductComponent';
import axios from 'axios';
import { useDispatch } from 'react-redux';
import { setProducts } from '../redux/actions/prodectActions';

const ProductListing = () => {
    const products = useSelector((state) => state);
    const dispatch = useDispatch();

        const fetchProducts = async() => {
            const response = await axios.get('https://fakestoreapi.com/products').catch((err) => {
                console.log("ERR", err)
            });
            dispatch(setProducts(response.data));
        };

        useEffect(()=> {
            fetchProducts();
        },[]);
    console.log("products:" ,products);
    return(
    <div className='ui grid container'>
        <ProductComponent/>
    </div>
    )
}

export default ProductListing

다시 실행한 뒤 콘솔창을 확인하면 데이터들이 모두 콘솔에 나타난 상태를 확인할 수 있다.

Render Products Listing Page

import React from "react";
import { useSelector } from "react-redux";

const ProductComponent = () => {
  const products = useSelector((state) => state.allProducts.products);
  const renderList = products.map((product) => {
    const { id, title, image, price, category } = product;
    return (
      <div className="four wide column" key={id} >
        <div className="ui link cards">
          <div className="card">
            <div className="image">
              <img src={image} alt={title} />
            </div>
            <div className="content">
              <div className="header">{title}</div>
              <div className="meta price">$ {price}</div>
              <div className="meta">{category}</div>
            </div>
          </div>
        </div>
      </div>
    );
  });

  return (
      <>
        {renderList}
      </>
  )
};

export default ProductComponent;

클릭 하고 난 뒤에 상세 정보를 보여주는 상세 페이지를 제작한다.

selectedProductReducer 생성

export const selectedProductReducer = (state={},{type, payload}) => {
    switch(type) {
        case ActionTypes.SELECTED_PRODUCT:
            return {...state, ...payload};
        default:
            return state;
    }
}

combineReducers에 해당 리듀서를 추가한다.

import { combineReducers } from "redux";
import { productReducer, selectedProductReducer } from "./productReducer";

const reducers = combineReducers({
    allProducts: productReducer,
    product:selectedProductReducer,
});


export default reducers;

상세 페이지를 작성한다. 이때 fetchProductDetail함수를 작성해서 해당 productId의 데이터만 가져와서 렌더링을 하는 구조로 작성을 했다.

import React, {useEffect} from 'react';
import {useParams} from 'react-router-dom'
import axios from 'axios';
import { useDispatch } from 'react-redux';
import { selectedProduct } from '../redux/actions/productActions';
import { useSelector } from 'react-redux';

const ProductDetail = () => {
    const product = useSelector((state) => state.product)
    const {image, title, price, category, description} = product
    const {productId} = useParams();
    const dispatch = useDispatch();
    console.log(product)

    const fetchProductDetail = async () => {
        const response = await axios.get(`https://fakestoreapi.com/products/${productId}`).catch(err => {
            console.log("Err", err);
        });

        dispatch(selectedProduct(response.data))
    }

    useEffect(() => {
        if(productId && productId !== "") fetchProductDetail();
    },[productId]);
    return (
        <div className="ui grid container">
          {Object.keys(product).length === 0 ? (
            <div>...Loading</div>
          ) : (
            <div className="ui placeholder segment">
              <div className="ui two column stackable center aligned grid">
                <div className="ui vertical divider">AND</div>
                <div className="middle aligned row">
                  <div className="column lp">
                    <img className="ui fluid image" src={image} />
                  </div>
                  <div className="column rp">
                    <h1>{title}</h1>
                    <h2>
                      <a className="ui teal tag label">${price}</a>
                    </h2>
                    <h3 className="ui brown block header">{category}</h3>
                    <p>{description}</p>
                    <div className="ui vertical animated button" tabIndex="0">
                      <div className="hidden content">
                        <i className="shop icon"></i>
                      </div>
                      <div className="visible content">Add to Cart</div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          )}
        </div>
      );
}

export default ProductDetail

현재 상황

원하는 항목을 클릭하면 상세페이지로 이동하면서 해당 항목의 상세페이지가 나타나지만, 뭔가 이상하다.
첫번째 항목을 클릭한뒤에 다른 항목을 클릭하면 일정시간동안 이전에 클릭한 항목의 정보가 남아있다가 업데이트 되는 모습을 볼수 있다.
따라서 상세 항목을 클릭했을 때 남아있는 항목의 정보를 지워야한다.

Select and Remove Action Types

그래서 이걸 만드는 것이다.
사용자가 항목을 선택하고 그다음 항목을 선택하면 로딩중에서 이전에 선택한 항목의 데이터를 전부 제거하는 것으로 이전의 정보가 남지 않게 해주는 역할을 한다.

selectedProductReducer내부에 case로 REMOVE_SELECTED_PRODUCT를 추가한다.
productReducer.js

export const selectedProductReducer = (state={},{type, payload}) => {
    switch(type) {
        case ActionTypes.SELECTED_PRODUCT:
            return {...state, ...payload};
        case ActionTypes.REMOVE_SELECTED_PRODUCT:
            return {};
        default:
            return state;
    }
}

그다음 액션 생성함수 removeSelectedProduct도 작성한다.
productActions.js

export const removeSelectedProduct = () => {
    return {
        type: ActionTypes.REMOVE_SELECTED_PRODUCT,
    };
};

마지막으로 ProductDetailuseEffect내용을 제거한다. 해당 내용을 렌더링 하기 전에 dispatchremoveSelectedProduct를 실행한다.

 useEffect(() => {
        if(productId && productId !== "") fetchProductDetail();
        return () => {
            dispatch(removeSelectedProduct());
        }
    },[productId]);

결과


이제 선택을 해도 이전의 정보가 남아있지 않고 원하는 데이터만 보여주게 된다.

profile
개발자 꿈나무

0개의 댓글