프로젝트를 하나 생성하고, react-router-dom
, axios
, react react-router
패키지 설치
프로젝트내부에 디렉토리를 생성하여 구조를 조정한다.
src 내부에 폴더와 파일을 생성한다.
containers: 컴포넌트들이 들어갈 폴더
redux : redux 관련 폴더와 파일들이 들어간다.
- actions : 선언한 Actions파일이 들어간다.
- constants: action-type파일이 들어간다.
reducers : reducer 관련 파일이 들어간다.
constants
폴더 내부에 action-types.js
파일을 생성하고 액션 유형을 작성할 것이다.
export const ActionTypes = {
SET_PRODUCTS : "SET_PRODUCTS",
SELECTED_PRODUCT: "SELECTED_PRODUCT",
REMOVESELECTED_PRODUCT: "REMOVE_SELECTED_PRODUCT",
}
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,
};
};
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
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;
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')
);
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
일단 간단하게만 제작하고 라우팅을 추가한 다음 상세하게 작성한다.
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;
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
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
다시 실행한 뒤 콘솔창을 확인하면 데이터들이 모두 콘솔에 나타난 상태를 확인할 수 있다.
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
현재 상황
원하는 항목을 클릭하면 상세페이지로 이동하면서 해당 항목의 상세페이지가 나타나지만, 뭔가 이상하다.
첫번째 항목을 클릭한뒤에 다른 항목을 클릭하면 일정시간동안 이전에 클릭한 항목의 정보가 남아있다가 업데이트 되는 모습을 볼수 있다.
따라서 상세 항목을 클릭했을 때 남아있는 항목의 정보를 지워야한다.
그래서 이걸 만드는 것이다.
사용자가 항목을 선택하고 그다음 항목을 선택하면 로딩중에서 이전에 선택한 항목의 데이터를 전부 제거하는 것으로 이전의 정보가 남지 않게 해주는 역할을 한다.
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,
};
};
마지막으로 ProductDetail
의 useEffect
내용을 제거한다. 해당 내용을 렌더링 하기 전에 dispatch
로 removeSelectedProduct
를 실행한다.
useEffect(() => {
if(productId && productId !== "") fetchProductDetail();
return () => {
dispatch(removeSelectedProduct());
}
},[productId]);
결과
이제 선택을 해도 이전의 정보가 남아있지 않고 원하는 데이터만 보여주게 된다.