이전 게시물에서 만들어 둔 client 폴더로 이동한다.
모듈을 추가해줄것임~
npm i react-router-dom
라우팅을 위해서 사용하는 모듈,,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"
></script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"
></script>
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
<!-- web font -->
<!-- 1 -->
<link
href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap"
rel="stylesheet"
/>
<!-- my css -->
<link rel="stylesheet" href="/style.css" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
웹 폰트 추가해주고 부트스트랩 cdn 추가해줌
client/src/components/Nav.jsx
import React from "react";
const Nav = () => {
return (
<nav className="navbar navbar-expand-sm navbar-light bg-light mb-3">
<div className="container">
<div className="navbar-brand">My Website</div>
<button
className="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarSupportedContent"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarSupportedContent">
<ul className="navbar-nav">
<li className="nav-item">
<a href="/" className="nav-link">
Home
</a>
</li>
<li className="nav-item">
<a href="/about" className="nav-link">
About
</a>
</li>
<li className="nav-item">
<a href="/board" className="nav-link">
Board
</a>
</li>
</ul>
</div>
</div>
</nav>
);
};
export default Nav;
import React from "react";
const Home = () => {
return (
<div className="container mb-3">
<div className="jumbotron">
<h1>MY WEBSITE</h1>
<p>HOME</p>
</div>
</div>
);
};
export default Home;
import React from "react";
const About = () => {
return (
<div className="container mb-3">
<h2 className="mb-3">About</h2>
</div>
);
};
export default About;
import React from "react";
import Nav from "./components/Nav";
import Home from "./routes/Home";
import About from "./routes/About";
import { Route, Routes } from "react-router-dom";
const App = () => {
return (
<div>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
};
export default App;
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
이게 젤 기본...
이제 Board로 이동하면 포스트 리스트를 출력하도록 코드를 작성한다.
// import 생략
const App = () => {
return (
<div>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/board" element={<PostList />} />
</Routes>
</div>
);
};
export default App;
Board 페이지로 이동하는 Route를 추가해준다.
src/routes/PostList.jsx
import React from "react";
const Board = () => {
return (
<div className="container mb-3">
<h2 className="mb-3">Board</h2>
<table className="board-table table table-sm border-bottom">
<thead className="thead-light">
<tr>
<th scope="col">Title</th>
<th scope="col" className="date">
Date
</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div>
<a className="btn btn-primary" href="/posts/new">
New
</a>
</div>
</div>
);
};
export default Board;
실행결과는 위와 같다.
이제 <tbody>
부분에 포스트들을 넣어줄거임
src/components/PostItem.jsx
import React from "react";
const PostItem = () => {
return (
<tr>
<td>
<a>
<div className="ellipsis">포스트 타이틀</div>
</a>
</td>
<td className="date">
<span>포스트 생성일</span>
</td>
</tr>
);
};
export default PostItem;
// 생략
</thead>
<tbody>
<PostItem />
</tbody>
</table>
<div>
<a className="btn btn-primary" href="/posts/new">
New
</a>
</div>
</div>
);
};
export default Board;
tbody에 포스트 아이템을 넣어주면 아래처럼 된다.
이제 포스트들을 DB에서 가져와보자~~
fetch
를 사용한다...
import React from "react";
import { useEffect, useState } from "react";
import PostItem from "../components/PostItem";
const PostList = () => {
const [posts, setPosts] = useState([]);
// 1.
async function getPosts() {
const response = await fetch("http://localhost:5500/posts");
if (!response.ok) {
const message = `An error occurred: ${response.statusText}`;
window.alert(message);
return;
}
// 2.
const postlist = await response.json();
setPosts(postlist);
}
useEffect(() => {
getPosts();
}, []);
// 3.
const postItems = posts.map((post, idx) => (
<PostItem post={post} key={idx} />
));
return (
<div className="container mb-3">
<h2 className="mb-3">Board</h2>
<table className="board-table table table-sm border-bottom">
<thead className="thead-light">
<tr>
<th scope="col">Title</th>
<th scope="col" className="date">
Date
</th>
</tr>
</thead>
<tbody>{postItems}</tbody>
</table>
<div>
<a className="btn btn-primary" href="/posts/new">
New
</a>
</div>
</div>
);
};
export default PostList;
getPosts()
서버주소인 "http://localhost:5500/posts"로 GET 요청을 보낸다
fetch
함수를 사용한다. 서버에 네트워크 요청을 보내고 새로운 정보를 받아오는 함수이다.
fetch 기본 문법
let promise = fetch(url, [options])
응답은 대개 두 단계를 거쳐 진행된다
서버에서 응답 헤더를 받자마자 fetch 호출 시 반환받은 promise가 내장 클래스 Response의 인스턴스와 함께 이행 상태가 된다
이 단계는 아직 본문(body)이 도착하기 전이지만, 개발자는 응답 헤더를 보고 요청이 성공적으로 처리되었는지 아닌지를 확인가능하다.
추가 메서드를 호출해 응답 본문을 받기
response 에는 프라미스를 기반으로 하는 다양한 메서드가 존재한다. 이 메서드들을 사용하면 다양한 형태의 응답 본문을 처리
- response.text()
: 응답을 읽고 텍스트를 반환
- response.json()
: 응답을 JSON 형태로 파싱
const postlist = await response.json();
setPosts(postlist);
서버에 보낸 요청에 대한 응답을 JSON 형태로 파싱하여 postlist 변수로 저장하여 posts에 저장한다.
const postItems = posts.map((post, idx) => (
<PostItem post={post} key={idx} />
));
posts 배열을 순회하면서 PostItem 컴포넌트를 반환한다. 이때 속성값으로 각 post를 지정해준다.
import React from "react";
const PostItem = (props) => {
const { post } = props;
return (
<tr>
<td>
<a>
<div className="ellipsis">{post.title}</div>
</a>
</td>
<td className="date">
<span>{post.createdAt}</span>
</td>
</tr>
);
};
export default PostItem;
PostList.jsx에서 전달받은 post를 이용하여 포스트 값을 나타낸다.
실행결과는 아래와 같다
Route를 추가해준다
<Route path="/posts/:id" element={<PostDetail />} />
:id
를 통해서 url에서 파라미터 값을 전달할 수 있게된다.
import React from "react";
const PostItem = (props) => {
const { post } = props;
return (
<tr>
<td>
<a href={`/posts/${post._id}`}>
<div className="ellipsis">{post.title}</div>
</a>
</td>
<td className="date">
<span>{post.createdAt}</span>
</td>
</tr>
);
};
export default PostItem;
a 태그에 post._id를 전달한다.
<a href={`/posts/${post._id}`}>
import React from "react";
const PostDetail = () => {
return (
<div className="container mb-3">
<nav aria-label="breadcrumb">
<ol className="breadcrumb p-1 pl-2 pr-2">
<li className="breadcrumb-item">
<a href="/">Home</a>
</li>
<li className="breadcrumb-item">
<a href="/posts">Board</a>
</li>
<li className="breadcrumb-item active" aria-current="page">
post.title
</li>
</ol>
</nav>
<div className="card">
<h5 className="card-header p-2">post.title</h5>
<div className="row">
<div className="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1">
<div className="post-body p-2">post.content</div>
</div>
<div className="col-md-5 col-lg-4 col-xl-3 order-sm-1 order-md-2">
<div className="post-info card m-2 p-2">
<div>
<span>Created</span> : <span>post.createdAt</span>
<br />
</div>
</div>
</div>
</div>
</div>
<div className="mt-3">
<a className="btn btn-primary" href="/posts">
Back
</a>
<a className="btn btn-primary">Edit</a>
<button className="btn btn-primary">Delete</button>
</div>
</div>
);
};
export default PostDetail;
여기까지 코드 작성 후에 결과를 확인해보면 아래와 같다. 포스트 리스트에서 하나의 포스트를 클릭하면 화면이 이동한다.
이제 하나의 포스트를 DB에서 추출해와서 이를 화면에 출력해주면 된다.
url 파라미터에서 id를 추출해서 해당 아이디를 이용하여 DB에서 포스트의 데이터를 추출한다
url 파라미터 사용을 위해서 useParams
를 사용한다.
const params = useParams();
console.log(params.id);
<Route path="/posts/:id" element={<PostDetail />} />
라우트 path에 설정해준 id 값을 가져오게 되는 것임
import React, { useEffect } from "react";
import { useState } from "react";
import { useParams } from "react-router-dom";
const PostDetail = () => {
const params = useParams();
const [post, setPost] = useState({});
async function fetchData() {
const id = params.id.toString();
const response = await fetch(`http://localhost:5500/posts/${id}`);
if (!response.ok) {
const message = `An error has occurred: ${response.statusText}`;
window.alert(message);
return;
}
const post = await response.json();
setPost(post);
}
useEffect(() => {
fetchData();
}, []);
return (
<div className="container mb-3">
<nav aria-label="breadcrumb">
<ol className="breadcrumb p-1 pl-2 pr-2">
<li className="breadcrumb-item">
<a href="/">Home</a>
</li>
<li className="breadcrumb-item">
<a href="/posts">Board</a>
</li>
<li className="breadcrumb-item active" aria-current="page">
{post.title}
</li>
</ol>
</nav>
<div className="card">
<h5 className="card-header p-2">{post.title}</h5>
<div className="row">
<div className="col-md-7 col-lg-8 col-xl-9 order-sm-2 order-md-1">
<div className="post-body p-2">{post.content}</div>
</div>
<div className="col-md-5 col-lg-4 col-xl-3 order-sm-1 order-md-2">
<div className="post-info card m-2 p-2">
<div>
<span>Created</span> : <span>{post.createdAt}</span>
<br />
{post.updatedAt ? (
<span>Updated : {post.updatedAt} </span>
) : (
<span></span>
)}
</div>
</div>
</div>
</div>
</div>
<div className="mt-3">
<a className="btn btn-primary" href="/posts">
Back
</a>
<a className="btn btn-primary" href={`/posts/${post._id}/edit`}>
<button className="btn btn-primary">Delete</button>
</div>
</div>
);
};
export default PostDetail;
실행결과
<Route path="/posts/:id/edit" element={<Edit />} />
<Route path="/posts/new" element={<New />} />
경로를 설정해준다
import React from "react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
const New = () => {
const navigate = useNavigate();
const [form, setForm] = useState({
title: "",
content: "",
});
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
async function onSubmit(e) {
e.preventDefault();
// fetch에 옵션 지정하여 서버에 요청을 전달한다.
await fetch(`http://localhost:5500/posts/`, {
method: "POST",
body: JSON.stringify(form),
headers: {
"Content-Type": "application/json",
},
});
// 수정 후 이전페이지로 돌아가기
navigate(`/posts/`);
}
return (
<div className="container mb-3">
<nav aria-label="breadcrumb">
<ol className="breadcrumb p-1 pl-2 pr-2">
<li className="breadcrumb-item">
<a href="/">Home</a>
</li>
<li className="breadcrumb-item">
<a href="/posts">Board</a>
</li>
<li className="breadcrumb-item active" aria-current="page">
Create Post
</li>
</ol>
</nav>
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
id="title"
name="title"
className="form-control"
value={form.title}
onChange={(e) => updateForm({ title: e.target.value })}
/>
</div>
<div className="form-group">
<label htmlFor="content">Content</label>
<textarea
id="content"
name="content"
rows="5"
className="form-control"
value={form.content}
onChange={(e) => updateForm({ content: e.target.value })}
></textarea>
</div>
<div>
<a className="btn btn-primary" href={`/posts/`}>
Back
</a>
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
};
export default New;
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
const Edit = () => {
// 파라미터를 받기 위해 사용
const params = useParams();
// 페이지 이동을 위해 사용
const navigate = useNavigate();
// 폼 상태를 관리한다.
const [form, setForm] = useState({
title: "",
content: "",
});
// url로 넘어온 파라미터를 이용하여 특정 포스트의 내옹을 DB에서 불러온다
async function fetchData() {
const id = params.id.toString();
const response = await fetch(`http://localhost:5500/posts/${id}`);
if (!response.ok) {
const message = `An error has occurred: ${response.statusText}`;
window.alert(message);
return;
}
const post = await response.json();
// 불러온 포스트의 내용을 상태저장
setForm(post);
}
// 폼 제출 시 이벤트
async function onSubmit(e) {
e.preventDefault();
const editedPost = {
title: form.title,
content: form.content,
};
// fetch에 옵션 지정하여 서버에 요청을 전달한다.
await fetch(`http://localhost:5500/posts/${params.id}`, {
method: "PUT",
body: JSON.stringify(editedPost),
headers: {
"Content-Type": "application/json",
},
});
// 수정 후 이전페이지로 돌아가기
navigate(`/posts/${form._id}`);
}
// 폼 상태 변화 제어
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
useEffect(() => {
fetchData();
}, []);
return (
<div className="container mb-3">
<nav aria-label="breadcrumb">
<ol className="breadcrumb p-1 pl-2 pr-2">
<li className="breadcrumb-item">
<a href="/">Home</a>
</li>
<li className="breadcrumb-item">
<a href="/posts">Board</a>
</li>
<li className="breadcrumb-item">
<a href={`/posts/${form._id}`}>{form.title}</a>
</li>
<li className="breadcrumb-item active" aria-current="page">
Edit Post
</li>
</ol>
</nav>
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="title">Title</label>
<input
type="text"
id="title"
name="title"
className="form-control"
value={form.title}
onChange={(e) => updateForm({ title: e.target.value })}
/>
</div>
<div className="form-group">
<label htmlFor="content">Content</label>
<textarea
id="content"
name="content"
rows="5"
className="form-control"
value={form.content}
onChange={(e) => updateForm({ content: e.target.value })}
></textarea>
</div>
<div>
<a className="btn btn-primary" href={`/posts/${form._id}`}>
Back
</a>
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
);
};
export default Edit;
추가랑 수정은 거의 비슷하다~~ fetch
할 때 보내주는 경로랑 메서드 확인!