Jieunny์ ๋ธ๋ก๊ทธ
S2) Unit 11. [์ค์ต] Coz’ Mini Hackathon (์๊ณ ๋ผ์คํ ์ด์ธ ์๋ฒ) ๋ณธ๋ฌธ
S2) Unit 11. [์ค์ต] Coz’ Mini Hackathon (์๊ณ ๋ผ์คํ ์ด์ธ ์๋ฒ)
Jieunny 2023. 2. 9. 16:52๐ฃ ๋๋ง์ ์๊ณ ๋ผ ์คํ ์ด์ธ ์๋ฒ ๋ง๋ค๊ธฐ
Bare Minimum Requirement Self Checklist
1๏ธโฃ my-agora-states-server
โ๏ธ my-agora-states-server/app.js
โ
๋ชจ๋ Origin, ๊ฒฝ๋ก์ ๋ํด CORS ์์ฒญ์ ํ์ฉํ๊ฒ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํฉ๋๋ค.
โ
POST ์์ฒญ ๋ฑ์ ํฌํจ๋ body(payload)๋ฅผ ๊ตฌ์กฐํํ๊ธฐ ์ํ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํฉ๋๋ค.
โ
์๋ฒ ์ํ ํ์ธ์ ์ํด / ์์ ์ํ ์ฝ๋ 200์ผ๋ก ์๋ตํฉ๋๋ค.
โ
discussionRouter ๋ฅผ ์ด์ฉํ์ฌ /discussions ๊ฒฝ๋ก๋ก ๋ผ์ฐํ
ํฉ๋๋ค.
โ๏ธ my-agora-states-server/router/discussions.js
โ
GET /discussions
โ
GET /discussions/:id
โ๏ธ my-agora-states-server/controller/index.js
โ
discussionsController.findAll
โ
discussionsController.findById
2๏ธโฃ my-agora-states-server ๊ณผ์ ์ ์ถ(Pull request)
โ๏ธ Pull request๋ก ๊ณผ์ ์ ์ถ
3๏ธโฃ my-agora-states-server ์์
โ๏ธ package.json ์ ์ฐธ๊ณ ํ์ฌ ๋๋ง์ ์๊ณ ๋ผ ์คํ
์ด์ธ ์๋ฒ๋ฅผ ๋ก์ปฌ ํ๊ฒฝ์์ ์คํํฉ๋๋ค.
4๏ธโฃ my-agora-states ์ ์ฐ๋ํ๊ธฐ
โ๏ธ my-agora-states-server๊ฐ ์ผ์ ธ ์๋์ง ํ์ธํฉ๋๋ค.
โ๏ธ ๋ก์ปฌ ํ๊ฒฝ์์ ์คํํ ๋๋ง์ ์๊ณ ๋ผ ์คํ
์ด์ธ ์๋ฒ์์ discussions ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค.
Optional Checklist
1๏ธโฃ my-agora-states-in-react
โ๏ธ create-react-app์ผ๋ก ํ๋ก์ ํธ ์์ฑ
โ๏ธ ๊ธฐ์กด์ ๋ง๋ ๋๋ง์ ์๊ณ ๋ผ ์คํ
์ด์ธ ๋ฅผ React๋ก ์ฎ๊ธฐ๊ธฐ
โ
๋์ค์ปค์
๋์ด ๊ธฐ๋ฅ
โ๏ธ ๋ก์ปฌ ํ๊ฒฝ์์ ์คํํ ๋๋ง์ ์๊ณ ๋ผ ์คํ
์ด์ธ ์๋ฒ์์ discussions ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค.
๐ฃ ์๋ฒ ๊ตฌํ
โ๏ธ my-agora-states-server
1๏ธโฃ controller/index.js
const { agoraStatesDiscussions } = require("../repository/discussions");
const discussionsData = agoraStatesDiscussions;
const discussionsController = {
findAll: (req, res) => {
// TODO: ๋ชจ๋ discussions ๋ชฉ๋ก์ ์๋ตํฉ๋๋ค.
return res.status(200).json(discussionsData);
},
findById: (req, res) => {
// TODO: ์์ฒญ์ผ๋ก ๋ค์ด์จ id์ ์ผ์นํ๋ discussion์ ์๋ตํฉ๋๋ค.
const { id } = req.params;
if(id){
const filteredList = discussionsData.filter((discussion) => {
return discussion.id === Number(id);
// id๋ฅผ number๋ก ๋ฐ๊ฟ์ค์ผ ๋น๊ต ๊ฐ๋ฅํ๋ค.
})
if(filteredList.length === 0) return res.status(404).send('Not found');
else return res.status(200).json(filteredList[0]);
}
}
};
module.exports = {
discussionsController,
};
2๏ธโฃ router/discussions.js
// TODO: discussions ๋ผ์ฐํฐ๋ฅผ ์์ฑํฉ๋๋ค.
const { discussionsController } = require('../controller');
const { findAll, findById } = discussionsController;
const express = require('express');
const router = express.Router();
// TODO: ๋ชจ๋ discussions ๋ชฉ๋ก์ ์กฐํํ๋ ๋ผ์ฐํฐ๋ฅผ ์์ฑํฉ๋๋ค.
router.get('/', findAll);
// TODO: :id์ ๋ง๋ discussion์ ์กฐํํ๋ ๋ผ์ฐํฐ๋ฅผ ์์ฑํฉ๋๋ค.
router.get('/:id', findById);
module.exports = router;
3๏ธโฃ App.js
const express = require('express');
const app = express();
const cors = require('cors');
const morgan = require('morgan');
// morgan ๋ฏธ๋ค์จ์ด๊ฐ ์ธํ
๋์ด ์์ต๋๋ค.
// HTTP ์์ฒญ logger๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฏธ๋ค์จ์ด ์
๋๋ค.
app.use(morgan('tiny'));
// TODO: cors๋ฅผ ์ ์ฉํฉ๋๋ค.
app.use(cors());
// TODO: Express ๋ด์ฅ ๋ฏธ๋ค์จ์ด์ธ express.json()์ ์ ์ฉํฉ๋๋ค.
app.use(express.json());
const port = 4000;
const discussionsRouter = require('./router/discussions');
// TODO: app.use()๋ฅผ ํ์ฉํ์ฌ /discussions ๊ฒฝ๋ก๋ก ๋ผ์ฐํ
ํฉ๋๋ค.
app.use('/discussions', discussionsRouter);
app.get('/', (req, res) => {
// ์๋ฒ ์ํ ํ์ธ์ ์ํด ์ํ ์ฝ๋ 200๊ณผ ํจ๊ป ์๋ต์ ๋ณด๋
๋๋ค.
res.status(200).send('fe-sprint-my-agora-states-server');
});
const server = app.listen(port, () => {
console.log(`[RUN] My Agora States Server... | http://localhost:${port}`);
});
module.exports.app = app;
module.exports.server = server;
๐ฃ ์๊ณ ๋ผ ์คํ ์ด์ธ ๋ฆฌ์กํธ ๊ตฌํ
โ๏ธ ์ฃผ์ ๊ธฐ๋ฅ
โฐ ์ปดํฌ๋ํธ๋ก ๋๋๊ธฐ
โฐ ์๋ฒ์์ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ
โฐ ๋ถ๋ฌ์จ ๋ฐ์ดํฐ 10๊ฐ์ฉ ํ์ด์ง๋ค์ด์
ํ๊ธฐ
โฐ ๋ฒํผ ๋๋ฅด๋ฉด ์๋ก์ด ๋ฐ์ดํฐ ์ถ๊ฐํ๊ธฐโฐ localStroage์ ์ ์ฅํ๊ธฐ (๊ตฌํ ํ๋๋ฐ ์ฒ์ ์ถ๊ฐํ ๋ฐ์ดํฐ๋ ์ ์ฅ์ด ์๋๊ณ ๋๋ฒ์งธ ๋ฐ์ดํฐ๋ถํฐ ์ ์ฅ๋๋ ์๋ฌ ๋ฐ์)
โ๏ธ ํ์ผ ๊ตฌ์กฐ
๏นmy-agora-states-react
แ src
แ components
แ Content.js
แ Discussions.js
แ DiscussionsContainer.js
แ Form.js
แ Main.js
แ Menubar.js
แ Paging.js
แ Tab.js
แ styles
แ images
แ App.js
แ App.css
โ๏ธ ์ปดํฌ๋ํธ ๊ตฌ์กฐ
โ๏ธ ๊ตฌํ ์ฝ๋
1๏ธโฃ ์๋ฒ์์ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ -> Contents.js
import Form from '../components/Form';
import '../styles/Contents.css';
import { useState, useEffect, useRef } from 'react';
import DiscussionsContainer from '../components/DiscussionsContainer';
function Contents() {
const [storageData, setStorageData] = useState([]);
useEffect(() => {
// ์ฒ์ ๋ ๋๋ง ํ ๋๋ง ์๋ฒ์์ ๋ฐ์ดํฐ ๋ถ๋ฌ์ค๊ธฐ ํ๊ธฐ
fetch('http://localhost:4000/discussions') // ์๋ฒ์ ๋ฐ์ดํฐ ๋ฐ์์ค๊ธฐ
.then(res => res.json())
.then(json => {
if(localStorage.length===0){
localStorage.setItem('Discussions', JSON.stringify(json));
setStorageData(JSON.parse(localStorage.getItem('Discussions')));
}
else{
setStorageData(JSON.parse(localStorage.getItem('Discussions')));
}
});
}, []);
//console.log(storageData);
return (
<div className="Contents">
<Form discussions={storageData} setDiscussions={setStorageData}/>
<DiscussionsContainer discussions={storageData}/>
</div>
);
}
export default Contents;
โฐ ๋๋ง์ ์๊ณ ๋ผ ์คํ
์ด์ธ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์จ ๋ค์, ๊ทธ ๋ฐ์ดํฐ๋ฅผ storageData์ ๋ด์์ Form๊ณผ DiscussionsContainer ์ปดํฌ๋ํธ์ ์ ๋ฌํด์ค๋ค.
2๏ธโฃ ๋ถ๋ฌ์จ ๋ฐ์ดํฐ 10๊ฐ์ฉ ํ์ด์ง๋ค์ด์ ํ๊ธฐ -> Paging.js / Discussions.js
๏น Paging.js
import React, { useState } from 'react';
import '../styles/Paging.css';
import Discussions from '../components/Discussions';
import Pagination from "react-js-pagination";
function Paging({ discussions }) {
const [page, setPage] = useState(1);
//console.log(discussions);
let discussionsCount = discussions.length;
let discussionPerPage = 10;
let totalPage = discussionsCount/discussionPerPage;
let dataPerPage = [];
for(let i=0; i<totalPage; i++){ //10๊ฐ์ฉ ๋๋ ์ ๋ฐฐ์ด์ ๋ฃ๊ธฐ
let temp = [];
for(let j=0; j<10; j++){
if(i*10 + j < discussionsCount) temp.push(discussions[i*10 + j])
}
dataPerPage.push(temp);
}
//console.log(discussions[0]);
const handlePageChange = (page) => {
setPage(page);
};
let pageData = dataPerPage[page-1];
// ๊ทธ ํ์ด์ง์ ํด๋นํ๋ ๋ฐ์ดํฐ 10๊ฐ๋ฅผ Discussions ์ปดํฌ๋ํธ๋ก ๋ณด๋ด์ค๋ค.
return (
<div className='Paging'>
<Pagination
activePage={page}
itemsCountPerPage={discussionPerPage}
totalItemsCount={discussionsCount}
pageRangeDisplayed={totalPage}
prevPageText={"<"}
nextPageText={">"}
onChange={handlePageChange}
/>
<Discussions currentPageData={pageData}/>
</div>
);
}
export default Paging;
โฐ ๋ฆฌ์กํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ react-js-pagination ์ ์ฌ์ฉํ๋ค.
โฐ props๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์ด์ 10๊ฐ์ฉ ๋๋ ์ ๋ฃ์ด์ค๋ค(1๋ฒ์งธ ํ์ด์ง์ ๋ฐ์ดํฐ๋ ๋ฐฐ์ด์ [0]๋ฒ์งธ์, 2๋ฒ์งธ ํ์ด์ง์ ๋ฐ์ดํฐ๋ ๋ฐฐ์ด์ [1]๋ฒ์งธ ์ด๋ฐ์์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ค)
โฐ page ๋ณ์์๋ ํ์ฌ ๋ด๊ฐ ๋๋ฅธ ํ์ด์ง์ ๊ฐ์ด ๋ด๊ฒจ ์์ผ๋ฏ๋ก, ๊ทธ ๊ฐ์ -1์ ํด๋นํ๋ ์ธ๋ฑ์ค์ ๋ฐ์ดํฐ๋ฅผ Discussions ์ปดํฌ๋ํธ์ ๋ณด๋ด์ค๋ค.
โฐ Pagination ์ปดํฌ๋ํธ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ฒ์ผ๋ก, ํ์ด์ง ๋ฆฌ์คํธ๋ฅผ ํ๋ฉด์ ๋์์ค๋ค.
๏น Discussions.js
function Discussions({ currentPageData }) {
//์คํ
์ดํธ๋ก ๋ฐ์ ํ์ด์ง ๋ณด์ฌ์ฃผ๊ธฐ.
//๋ฐ์ดํฐ๋ฅผ ํ์ด์ง์ ๋ฃ์ ๋งํผ๋ง ๋ฐ์์ค๊ธฐ.
//console.log(currentPageData)
return (
<div className="Discussions">
<ul className="discussions__container">
{currentPageData && currentPageData.map((data) => {
return(
<li className="discussion__container" key={data.id}>
<div className="discussion__avatar--wrapper"><img className="discussion__avatar--image" alt="avatarImg" src={data.avatarUrl}/></div>
<div className="discussion__content">
<h2 className="discussion__title"><a href={data.url}>{data.title}</a></h2>
<div className="discussion__information">{data.author + '/' + new Date(data.createdAt).toLocaleString()}</div>
</div>
{data.answer !== null ? <div className="answered__check"><a href={data.answer.url}>๐ฌ</a></div> : <div className="answered__check"></div>}
</li>
)
})
}
</ul>
</div>
);
}
export default Discussions;
โฐ Discussions ์ปดํฌ๋ํธ์์๋ 10๊ฐ์ฉ ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ์ด๋ฆ, ๋ ์ง ๋ฑ๋ฑ์ ๋ณด์ฌ์ค๋ค.
3๏ธโฃ ๋ฒํผ ๋๋ฅด๋ฉด ์๋ก์ด ๋ฐ์ดํฐ ์ถ๊ฐํ๊ธฐ -> Form.js
import '../styles/Form.css';
import Menubar from '../components/Menubar';
import { useEffect } from 'react';
function Form({discussions, setDiscussions}) {
function inputDiscussion(){
let nameValue = document.querySelector('#name').value;
let titleValue = document.querySelector('#title').value;
let storyValue = document.querySelector('#story').value;
let currentTime = new Date();
const newDiscussion = {
id: discussions.length + 10,
createdAt: currentTime,
author: nameValue,
title: titleValue,
body: storyValue,
url: "https://github.com/codestates-seb/agora-states-fe/discussions/45",
avatarUrl: "https://avatars.githubusercontent.com/u/79903256?s=64&v=4",
answer: null // ์ ๋ฃ์ด์ฃผ๋ฉด answer null ์๋๊ฑฐ if๋ฌธ์ ๊ฑธ๋ ค์ ์ค๋ฅ๋๋ค.
};
setDiscussions([newDiscussion, ...discussions]);
// ์ ๋ฐ์ดํฐ๋ฅผ ์๋ ๋ฐ์ดํฐ์ ์ถ๊ฐํด์ค๋ค.
localStorage.setItem('Discussions', JSON.stringify(discussions));
}
return (
<div className="Form">
<Menubar text={'Your Question'}/>
<form action="" method="get" className="form">
<div className="form__text">
<p>ENTER HERE</p>
</div>
<div className="form__input--wrapper">
<div className="form__input--name">
<label htmlFor="name"></label>
<input type="text" placeholder="name" name="name" id="name" autoComplete="off" required></input>
</div>
<div className="form__input--title">
<label htmlFor="name"></label>
<input type="text" placeholder="title" name="title" id="title" autoComplete="off" required></input>
</div>
<div className="form__textbox">
<label htmlFor="story"></label>
<textarea id="story" name="story" placeholder="Write your Question" autoComplete="off" required></textarea>
</div>
<div className="form__submit">
<input onClick={inputDiscussion} className="inputBtn" type="button" value="Question"></input>
</div>
</div>
</form>
</div>
);
}
export default Form;
โฐ spread ๋ฌธ๋ฒ์ ์ฌ์ฉํด์, ์ ๋ฐ์ดํฐ๋ฅผ ์๋ ๋ฐ์ดํฐ์ ๋ฃ์ด์ค๋ค.
4๏ธโฃ localStorage ์ ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ -> Content.js, Form.js
โฐ Content.js ์์ ์ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ๋๋ฐ ๋ง์ฝ localStorage์ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๊ทธ๊ฑธ ๋ถ๋ฌ์ค๊ณ ์๋๋ฉด, ๋ถ๋ฌ์จ ๋ฐ์ดํฐ๋ฅผ localStroage์ ์ ์ฅํ๋ค.
โฐ Form.js ์์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๋ฉด ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์ด์ ์ถ๊ฐํ๊ณ , ๊ทธ ๋ฐฐ์ด์ ๋ค์ localStroage์ ์๋ ๋ฐ์ดํฐ์ ๋ถ์ฌ๋ฃ๋๋ค.
โฐ ์ถ๊ฐ๊ฐ ๋๊ธด ํ๋๋ฐ, ์ฒซ ๋ฒ์งธ ์ถ๊ฐํ ๋ฐ์ดํฐ๋ ๋ฌด์๋๊ณ ๋ ๋ฒ์งธ ์ถ๊ฐํ ๋ฐ์ดํฐ๋ถํฐ ์ ์์ ์ผ๋ก ์ถ๊ฐ๋๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
โฐ ์์ง ํด๊ฒฐ์ ํ์ง ๋ชปํ๋ค..๋ญ๊ฐ ๋ฆฌ์กํธ ๋ ๋๋ง ๊ณผ์ ์์ ์๊ธฐ๋ ๋ฌธ์ ๊ฐ์๋ฐใ
ใ
ใ
ใ
๐ฃ ์์ฐ ํ๋ฉด
'CodeStates > Training' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
S3) Unit 1. [์ค์ต] Tree UI (0) | 2023.02.14 |
---|---|
S3) Unit 1. [์ค์ต] JSON.stringify (0) | 2023.02.14 |
S2) Unit 10. [์ค์ต] StatesAirline Server (0) | 2023.02.08 |
S2) Unit 10. [์ค์ต] Mini Node Server (0) | 2023.02.06 |
S2) Unit 9. [์ค์ต] StateAirline Client (0) | 2023.02.03 |