LAMP-SHOPPING-CLIENT
1. customHook 폴더 생성 후 useAsync.js 파일 생성
customHook/useASync.js
import { useReducer, useEffect, useCallback } from "react"
const initialState = {
loading: false,
data: null,
error: null
}
// 로딩중? 데이터 받기 성공? 데이터 받기 실패
// LOADING , SUCCESS, ERROR
function reducer(state, action){
switch(action.type) {
case "LOADING":
return {
loading: true,
data: null,
error: null
};
case "SUCCESS":
return {
loading: false,
data: action.data,
error: null
}
case "ERROR":
return {
loading: false,
data: null,
error: action.error
}
default:
return state;
}
}
function useAsync(callback, deps=[]) {
const [state, dispatch] = useReducer(reducer, initialState);
const fetchDate = async () => {
dispatch({type: "LOADING"});
try {
const data = await callback();
dispatch({
type: "SUCCESS",
data: data
})
}
catch(e){
dispatch({
type: "ERROR",
error: e
})
}
}
useEffect(()=>{
fetchDate();
}, deps);
return [state, fetchDate];
}
export default useAsync
main/index.js
import React from 'react';
import './index.scss';
import axios from 'axios';
// import useAsync from './useAsync';
import useAsync from '../customHook/useAsync';
import MainProduct from './MainProduct';
// 비동기
async function getProducts() {
const response = await axios.get("http://localhost:3000/products");
return response.data;
}
const MainPage = (props) => {
// * 쌤 (비동기)
const [state, refetch ] = useAsync(getProducts, [])
const { loading, data:products, error} = state;
if(loading) return <div>로딩중 ......</div>
if(error) return <div>에러가 발생했습니다.</div>
if(!products) return <div>로딩중입니다.</div>
// * 김효진 (비동기)
// const [ state ] = useAsync(getProducts);
// const { loading, data:products, error } = state;
// if(loading) return <div>로딩중...</div>
// if(error) return <div>에러가 발생했습니다.</div>
// if(!products) return <div>상품이 없습니다.</div>;
// 원래 받아오던 방법 (비동기 x)
// const [ products, setProducts ] = useState([]);
// useEffect(()=>{
// axios.get("http://localhost:3000/products")
// .then((result)=>{
// const products = result.data;
// setProducts(products);
// }).catch((e)=>{
// console.log(e);
// })
// }, [])
// if(products === [] ) return <div>로딩중입니다.</div>
return (
<div>
<div id="main">
<div id="banner">
{/* 바로 images 하면 public에 있는 images 들어감 */}
<img src="images/banners/banner1.png" alt="" />
</div>
<div id="product-list" className='inner'>
<h2>그린조명 최신상품</h2>
<div id="product-items">
{/* 나중에 map 이용해서 밑에꺼 8개 뿌려줄거임 */}
{products.map(product => <MainProduct key = {product.id} product={product} />)}
</div>
</div>
</div>
</div>
);
};
export default MainPage;
2. product/index.js
import React from 'react';
import "./product.scss"
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useAsync from '../customHook/useAsync'
async function getProduct(id) {
const response = await axios.get(`http://localhost:3000/product/${id}`)
return response.data;
}
const ProductPage = (props) => {
// product /1
const { id } = useParams();
const [state] = useAsync(()=>getProduct(id), [id]);
const { loading, data:product, error } = state;
if(loading) return <div>로딩중입니다.....</div>
if(error) return <div>에러가 발생했습니다.</div>
if(!product) return null;
// useEffect(비동기 x)
// const [ product, setProduct ] = useState(null);
// // useParams() 실행되면 파라미터 값을 가지고 있는 객체를 반환
// // product/1
// const { id } = useParams();
// // useEffect(function(){
// // axios.get(`http://localhost:3000/product/${id}`)
// // .then(result=> {
// // console.log(result);
// // const data = result.data;
// // setProduct(data);
// // })
// // .catch(e=> {
// // console.log(e);
// // })
// // }, []) // 빈 배열 넣어줘야 마운트 될 때 한번만 시행
// // if(!product) return <div>로딩중입니다...</div>
return (
<div className='inner'>
<div id="image-box">
<img src={product.imageUrl} alt =""/>
</div>
<div id="profile-box">
<ul>
<li>
<div>
<img src="/images/icons/avatar.png" alt=""/>
<span>{product.seller}</span>
</div>
</li>
<li>
{product.name}
</li>
<li>
가격 {product.price}
</li>
<li>등록일 2022년 6월 2일</li>
<li>상세설명</li>
</ul>
</div>
</div>
);
};
export default ProductPage;
LAMP-SHOPPING-SERVER
3. server.js 에 상품등록 부분 추가하기
// 상품 등록
app.post("/products", (req, res)=> {
// http body에 있는 데이터
const body = req.body;
// body객체에 있는 값을 각각 변수에 할당
const { name, price, seller, imageUrl } = body;
if(!name || !price || !seller) {
res.send("모든 필드를 입력해주세요");
}
// Product테이블에 레코드를 삽입
else {
models.Product.create({
name,
price,
seller,
imageUrl
}).then(result=>{
console.log("상품 생성 결과 :", result);
res.send({
result
})
})
.catch(e=>{
console.error(e);
res.send("상품 업로드에 문제가 생겼습니다.")
})
}
})
4. postMan으로 server 검사
5. server.js에 multer 선언
이미지 등록 부분 추가
// 업로드 이미지를 관리하는 스토리지 서버를 연결 -> multer를 사용하겠다
const multer = require("multer");
// 이미지 파일이 요청 오면 어디에 저장할건지 지정
const upload = multer({
storage: multer.diskStorage({
destination: function(req, file, cb) {
// 어디에 저장할거냐? upload/
cb(null, 'upload/')
},
filename: function(req, file, cb){
// 어떤 이름으로 저장할거야?
// file 객체의 오리지널 이름으로 저장하겠다.
cb(null, file.originalname)
}
})
})
// 이미지파일이 post로 요청이 왔을 때 upload라는 폴더에 이미지를 저장하기
// 이미지가 하나일 때 single
app.post('/image', upload.single('image'), (req, res)=>{
const file = req.file;
console.log(file);
res.send({
imageUrl: file.path
})
})
LAMP-SHOPPING-CLIENT
6.upload/index.js
import React, { useState } from 'react';
import './upload.scss';
import 'antd/dist/antd.css';
import { Form, Divider, Input, InputNumber, Button, Upload } from 'antd';
const Uploadpage = (props) => {
// 이미지 경로 상태관리 추가
const [imageUrl, setImageUrl ] = useState(null);
// 이미지 처리함수
const onChangeImage = (info) => {
// 파일이 업로드 중일 때
console.log(info.file)
if(info.file.status === "uploading"){
return;
}
// 파일이 업로드 완료 되었을 때
if(info.file.status === "done") {
const response = info.file.response;
const imageUrl = response.imageUrl;
// 받은 이미지경로를 imageUrl에 넣어줌
setImageUrl(imageUrl);
}
}
return (
<div id="upload-container" className='inner'>
<Form name="productUpload">
<Form.Item name="imgUpload"
label={<div className='upload-label'>상품사진</div>}>
<Upload name="image" action="http://localhost:3000/image"
listType="picture" showUploadList={false} onChange={onChangeImage}>
{/* 업로드 이미지가 있으면 이미지를 나타내고 업로드 이미지가 없으면
회색배경에 업로드 아이콘이 나타나도록 ... */}
{ imageUrl ? <img src={imageUrl}
alt="" width= "200px" height= "200px" /> :
(<div id="upload-img-placeholder">
<img src="images/icons/camera.png" alt="" />
<span>이미지를 업로드 해주세요.</span>
</div>)}
</Upload>
</Form.Item>
<Divider/>
<Form.Item name="seller"
label={<div className='upload-label'>판매자명</div>}>
<Input className="nameUpload" size='large'
placeholder='판매자 이름을 입력하세요'/>
</Form.Item>
<Divider/>
<Form.Item name="name"
label={<div className='upload-label'>상품이름</div>}>
<Input
className='upload-name'
size='large'
placeholder='상품 이름을 입력해주세요'/>
</Form.Item>
<Divider/>
<Form.Item name="price"
label={<div className='upload-label'>상품가격</div>}>
<InputNumber defaultValue={0} size="large"/>
</Form.Item>
<Divider/>
<Form.Item name="description"
label={<div className='upload-label'>상품소개</div>}>
<Input.TextArea
size='large'
id = "product-description"
maxLength={300}
placeholder="상품 소개를 적어주세요"
/>
</Form.Item>
<Form.Item>
<Button id="submit-button" size="large" htmlType='submit'>
상품등록하기
</Button>
</Form.Item>
</Form>
</div>
);
};
export default Uploadpage;
7.upload/index.js
서버로 데이터 전송하는 부분 / 리다이랙션 구현
import React, { useState } from 'react';
import './upload.scss';
import 'antd/dist/antd.css';
import { Form, Divider, Input, InputNumber, Button, Upload } from 'antd';
// import useAsync from '../customHook/useAsync'
import axios from "axios";
import { useNavigate } from 'react-router-dom';
// async function postProduct(values){
// const response = await axios.post(`http://localhost:3000/products`, {
// name: values.name,
// seller: values.seller,
// price: values.price,
// imageUrl: imageUrl // 상태관리 되고 있는 imageurl
// });
// return response.data;
// }
const Uploadpage = (props) => {
const navigate = useNavigate();
// 이미지 경로 상태관리 추가
const [imageUrl, setImageUrl ] = useState(null);
// 이미지 처리함수
const onChangeImage = (info) => {
// 파일이 업로드 중일 때
console.log(info.file)
if(info.file.status === "uploading"){
return;
}
// 파일이 업로드 완료 되었을 때
if(info.file.status === "done") {
const response = info.file.response;
const imageUrl = response.imageUrl;
// 받은 이미지경로를 imageUrl에 넣어줌
setImageUrl(imageUrl);
}
}
const onSubmit = (values) => {
// 서버로 데이터 전송하기
axios.post("http://localhost:3000/products", {
name: values.name,
seller: values.seller,
price: values.price,
imageUrl: imageUrl
}).then((result)=>{
console.log(result)
navigate("/");
})
.catch(e=>{
console.log(e);
})
}
return (
<div id="upload-container" className='inner'>
<Form name="productUpload" onFinish={onSubmit}>
<Form.Item name="imgUpload"
label={<div className='upload-label'>상품사진</div>}>
<Upload name="image" action="http://localhost:3000/image"
listType="picture" showUploadList={false} onChange={onChangeImage}>
{/* 업로드 이미지가 있으면 이미지를 나타내고 업로드 이미지가 없으면
회색배경에 업로드 아이콘이 나타나도록 ... */}
{ imageUrl ? <img src={imageUrl}
alt="" width= "200px" height= "200px" /> :
(<div id="upload-img-placeholder">
<img src="images/icons/camera.png" alt="" />
<span>이미지를 업로드 해주세요.</span>
</div>)}
</Upload>
</Form.Item>
<Divider/>
<Form.Item name="seller"
label={<div className='upload-label'>판매자명</div>}>
<Input className="nameUpload" size='large'
placeholder='판매자 이름을 입력하세요'/>
</Form.Item>
<Divider/>
<Form.Item name="name"
label={<div className='upload-label'>상품이름</div>}>
<Input
className='upload-name'
size='large'
placeholder='상품 이름을 입력해주세요'/>
</Form.Item>
<Divider/>
<Form.Item name="price"
label={<div className='upload-label'>상품가격</div>}>
<InputNumber defaultValue={0} size="large"/>
</Form.Item>
<Divider/>
<Form.Item name="description"
label={<div className='upload-label'>상품소개</div>}>
<Input.TextArea
size='large'
id = "product-description"
maxLength={300}
placeholder="상품 소개를 적어주세요"
/>
</Form.Item>
<Form.Item>
<Button id="submit-button" size="large" htmlType='submit'>
상품등록하기
</Button>
</Form.Item>
</Form>
</div>
);
};
export default Uploadpage;
8. 데이터베이스 삭제 후 description 컬럼 추가
LAMP-SHOPPING-CLIENT
models/products.js
// Common.js 구문 내보내기
// module.exprors
// 테이블을 모델링하는 파일
module.exports = function (sequelize, DataTypes) {
// 컬럼 name, price, imageUrl, seller
// 제약조건 allowNull: 컬럼의 값이 없어도 되는지 여부 (default: true)
const product = sequelize.define('Product', {
name: {
type: DataTypes.STRING(20),
allowNull: false
},
price: {
type: DataTypes.INTEGER(20),
allowNull: false
},
imageUrl: {
type: DataTypes.STRING(500),
},
seller: {
type: DataTypes.STRING(200),
allowNull: false
},
description: {
type: DataTypes.STRING(500),
allowNull: false
}
});
return product;
}
upload/index.js
import React, { useState } from 'react';
import './upload.scss';
import 'antd/dist/antd.css';
import { Form, Divider, Input, InputNumber, Button, Upload } from 'antd';
// import useAsync from '../customHook/useAsync'
import axios from "axios";
import { useNavigate } from 'react-router-dom';
// async function postProduct(values){
// const response = await axios.post(`http://localhost:3000/products`, {
// name: values.name,
// seller: values.seller,
// price: values.price,
// imageUrl: imageUrl // 상태관리 되고 있는 imageurl
// });
// return response.data;
// }
const Uploadpage = (props) => {
const navigate = useNavigate();
// 이미지 경로 상태관리 추가
const [imageUrl, setImageUrl ] = useState(null);
// 이미지 처리함수
const onChangeImage = (info) => {
// 파일이 업로드 중일 때
console.log(info.file)
if(info.file.status === "uploading"){
return;
}
// 파일이 업로드 완료 되었을 때
if(info.file.status === "done") {
const response = info.file.response;
const imageUrl = response.imageUrl;
// 받은 이미지경로를 imageUrl에 넣어줌
setImageUrl(imageUrl);
}
}
const onSubmit = (values) => {
// 서버로 데이터 전송하기
axios.post("http://localhost:3000/products", {
name: values.name,
seller: values.seller,
price: values.price,
imageUrl: imageUrl,
description: values.description
}).then((result)=>{
console.log(result)
navigate("/");
})
.catch(e=>{
console.log(e);
})
}
return (
<div id="upload-container" className='inner'>
<Form name="productUpload" onFinish={onSubmit}>
<Form.Item name="imgUpload"
label={<div className='upload-label'>상품사진</div>}>
<Upload name="image" action="http://localhost:3000/image"
listType="picture" showUploadList={false} onChange={onChangeImage}>
{/* 업로드 이미지가 있으면 이미지를 나타내고 업로드 이미지가 없으면
회색배경에 업로드 아이콘이 나타나도록 ... */}
{ imageUrl ? <img src={imageUrl}
alt="" width= "200px" height= "200px" /> :
(<div id="upload-img-placeholder">
<img src="images/icons/camera.png" alt="" />
<span>이미지를 업로드 해주세요.</span>
</div>)}
</Upload>
</Form.Item>
<Divider/>
<Form.Item name="seller"
label={<div className='upload-label'>판매자명</div>}>
<Input className="nameUpload" size='large'
placeholder='판매자 이름을 입력하세요'/>
</Form.Item>
<Divider/>
<Form.Item name="name"
label={<div className='upload-label'>상품이름</div>}>
<Input
className='upload-name'
size='large'
placeholder='상품 이름을 입력해주세요'/>
</Form.Item>
<Divider/>
<Form.Item name="price"
label={<div className='upload-label'>상품가격</div>}>
<InputNumber defaultValue={0} size="large"/>
</Form.Item>
<Divider/>
<Form.Item name="description"
label={<div className='upload-label'>상품소개</div>}>
<Input.TextArea
size='large'
id = "product-description"
maxLength={300}
placeholder="상품 소개를 적어주세요"
/>
</Form.Item>
<Form.Item>
<Button id="submit-button" size="large" htmlType='submit'>
상품등록하기
</Button>
</Form.Item>
</Form>
</div>
);
};
export default Uploadpage;
product/index.js
import React from 'react';
import "./product.scss"
import axios from 'axios';
import { useParams } from 'react-router-dom';
import useAsync from '../customHook/useAsync'
async function getProduct(id) {
const response = await axios.get(`http://localhost:3000/product/${id}`)
return response.data;
}
const ProductPage = (props) => {
// product /1
const { id } = useParams();
const [state] = useAsync(()=>getProduct(id), [id]);
const { loading, data:product, error } = state;
if(loading) return <div>로딩중입니다.....</div>
if(error) return <div>에러가 발생했습니다.</div>
if(!product) return null;
// useEffect(비동기 x)
// const [ product, setProduct ] = useState(null);
// // useParams() 실행되면 파라미터 값을 가지고 있는 객체를 반환
// // product/1
// const { id } = useParams();
// // useEffect(function(){
// // axios.get(`http://localhost:3000/product/${id}`)
// // .then(result=> {
// // console.log(result);
// // const data = result.data;
// // setProduct(data);
// // })
// // .catch(e=> {
// // console.log(e);
// // })
// // }, []) // 빈 배열 넣어줘야 마운트 될 때 한번만 시행
// // if(!product) return <div>로딩중입니다...</div>
return (
<div className='inner'>
<div id="image-box">
<img src={product.imageUrl} alt =""/>
</div>
<div id="profile-box">
<ul>
<li>
<div>
<img src="/images/icons/avatar.png" alt=""/>
<span>{product.seller}</span>
</div>
</li>
<li>
{product.name}
</li>
<li>
가격 {product.price}
</li>
<li>등록일</li>
<li>상세설명</li>
<li>{product.description}</li>
</ul>
</div>
</div>
);
};
export default ProductPage;
LAMP-SHOPPING-SERVER
server.js
const express = require("express");
const cors = require("cors");
const app = express();
const port = 3000;
const models = require('./models');
// 업로드 이미지를 관리하는 스토리지 서버를 연결 -> multer를 사용하겠다
const multer = require("multer");
// 이미지 파일이 요청 오면 어디에 저장할건지 지정
const upload = multer({
storage: multer.diskStorage({
destination: function(req, file, cb) {
// 어디에 저장할거냐? upload/
cb(null, 'upload/')
},
filename: function(req, file, cb){
// 어떤 이름으로 저장할거야?
// file 객체의 오리지널 이름으로 저장하겠다.
cb(null, file.originalname)
}
})
})
// json형식의 데이터를 처리할 수 있게 설정
app.use(express.json());
// 브라우저 cors 이슈를 막기 위해 사용(모든 브라우저의 요청을 일정하게 받겠다)
app.use(cors());
// upload 폴더에 있는 파일에 접근할 수 있도록 설정
app.use("/upload", express.static("upload"));
// 요청처리
// app.메서드(url, 함수)
// 이미지파일이 post로 요청이 왔을 때 upload라는 폴더에 이미지를 저장하기
// 이미지가 하나일 때 single
app.post('/image', upload.single('image'), (req, res)=>{
const file = req.file;
console.log(file);
res.send({
imageUrl: "http://localhost:3000/"+file.destination+file.filename
})
})
app.get('/products',async(req,res)=>{
// 데이터베이스 조회하기
models.Product.findAll()
.then(result=> {
// console.log("제품전체조회", result);
res.send(result);
})
.catch(e=>{
console.error(e)
res.send("파일 조회에 문제가 있습니다.")
})
})
// method는 get이고 오고 url은 /product/2 로 요청이 온 경우
app.get('/product/:id', async (req, res) => {
const params = req.params;
// const { id } = params;
// 하나만 조회할때는 findDone -> select문
models.Product.findOne({
// 조건절
where: {
id: params.id
}
})
.then(result=>{
res.send(result);
})
.catch(e=>{
console.log(e)
res.send("상품 조회에 문제가 생겼습니다.")
})
// const product = {
// id: id,
// name: "서버에서 보내는 이름",
// price: 50000,
// imgsrc:"images/products/product4.jpg",
// seller: "green",
// }
// res.send(product);
});
// app.post('/green', async (req, res)=>{
// console.log(req);
// res.send('그린 게시판에 게시글이 등록되었습니다.');
// });
// 상품 등록
app.post("/products", (req, res)=> {
// http body에 있는 데이터
const body = req.body;
// body객체에 있는 값을 각각 변수에 할당
const { name, price, seller, imageUrl, description } = body;
if(!name || !price || !seller) {
res.send("모든 필드를 입력해주세요");
}
// Product테이블에 레코드를 삽입
else {
models.Product.create({
name,
price,
seller,
imageUrl,
description
}).then(result=>{
console.log("상품 생성 결과 :", result);
res.send({
result
})
})
.catch(e=>{
console.error(e);
res.send("상품 업로드에 문제가 생겼습니다.")
})
}
})
// 실행
app.listen(port, ()=>{
console.log('쇼핑몰 서버가 동작중입니다.');
// sequelize와 데이터베이스 연결작업
// 데이터베이스 동기화
models.sequelize
.sync()
.then(()=> {
console.log('DB연결 성공');
})
.catch(e=>{
console.error(e);
console.log('DB연결 에러');
// 서버실행이 안되면 프로세서를 종료
process.exit();
})
})
'Stack > React' 카테고리의 다른 글
[React] rest (0) | 2022.07.05 |
---|---|
[React] Lamp 쇼핑몰 구현하기 6 (삭제기능 / carousel 구현) (0) | 2022.07.04 |
[React] 파일 업로드 관리하기 (0) | 2022.07.04 |
[React] 상태관리(useState / useReducer) 복습 (0) | 2022.07.04 |
[React] npm start 오류(throw err;) 해결하기 (0) | 2022.07.01 |