728x90
반응형
이미 backend 구현을 마치고 회원가입, 로그인까지 구현을 마쳤으니 기본적인 게시판을 만들어보려한다.
| 게시판 목록 페이지 작성
- 백엔드의 /api/posts에 요청해 게시글 목록을 가져온다.
- 제목, 작성자, 작성일을 보여준다.
- 제목을 클릭하면 해당 게시글 상세 페이지로 이동한다.
frontend/pages/PostListPage.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
const PostListPage = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPosts = async () => {
try {
const res = await axios.get('http://localhost:5000/api/posts');
setPosts(res.data);
} catch (err) {
console.error('게시글 목록 로드 실패:', err);
} finally {
setLoading(false);
}
};
fetchPosts();
}, []);
if (loading) return <p>로딩 중...</p>;
return (
<div>
<h2>📚 게시판</h2>
{posts.length === 0 ? (
<p>게시글이 없습니다.</p>
) : (
<ul>
{posts.map((post) => (
<li key={post._id}>
<Link to={`/posts/${post._id}`}>
<strong>{post.title}</strong>
</Link>{' '}
<span>- {post.author.username}</span>{' '}
<span>({new Date(post.createdAt).toLocaleDateString()})</span>
</li>
))}
</ul>
)}
</div>
);
};
export default PostListPage;
| 게시글 상세페이지 작성
- 게시글 ID를 기반으로 /api/posts/:id에서 데이터를 요청한다.
- 제목, 내용, 작성자, 작성일을 보여준다.
frontend/src/postes/PostDetailPage.js
import React, { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import axios from 'axios';
const PostDetailPage = () => {
const { id } = useParams(); // URL 파라미터에서 post ID 추출
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPost = async () => {
try {
const res = await axios.get(`http://localhost:5000/api/posts/${id}`);
setPost(res.data);
} catch (err) {
console.error('게시글 조회 실패:', err);
} finally {
setLoading(false);
}
};
fetchPost();
}, [id]);
if (loading) return <p>로딩 중...</p>;
if (!post) return <p>게시글을 찾을 수 없습니다.</p>;
return (
<div>
<h2>{post.title}</h2>
<p><strong>작성자:</strong> {post.author.username}</p>
<p><strong>작성일:</strong> {new Date(post.createdAt).toLocaleDateString()}</p>
<hr />
<p>{post.content}</p>
<br />
<Link to="/posts">← 목록으로 돌아가기</Link>
</div>
);
};
export default PostDetailPage;
| 게시글 작성 페이지
게시글은 로그인한 사용자만 입력할 수 있게 context를 사용한다.
frontend/src/pages/CreatePostPage.js
import React, { useState, useContext } from 'react';
import axios from 'axios';
import { AuthContext } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
const CreatePostPage = () => {
const { user } = useContext(AuthContext);
const navigate = useNavigate();
const [formData, setFormData] = useState({ title: '', content: '' });
const [message, setMessage] = useState('');
const handleChange = (e) => {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const token = localStorage.getItem('token');
const res = await axios.post(
'http://localhost:5000/api/posts',
formData,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
setMessage('글이 성공적으로 작성되었습니다.');
console.log('작성된 글: ', res.data)
navigate('/posts');
} catch (err) {
console.error(err);
setMessage('글 작성 실패 ❌');
}
};
if (!user) {
return <p>로그인이 필요한 기능입니다. 🔒</p>;
}
return (
<div>
<h2>게시글 작성 ✍️</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
placeholder="제목"
onChange={handleChange}
required
/>
<br />
<textarea
name="content"
placeholder="내용"
onChange={handleChange}
rows="10"
cols="50"
required
/>
<br />
<button type="submit">등록</button>
</form>
<p>{message}</p>
</div>
);
};
export default CreatePostPage;
| 라우트 등록
frontend/src/App.js
<Route path="/posts" element={<PostListPage />} />
<Route path="/posts/:id" element={<PostDetailPage />} />
<Route path="/posts/create"
element={
<ProtectedRoute>
<CreatePostPage />
</ProtectedRoute>
}
/>
http://localhost:3000/posts
아직은 글이 없어서 이와 같은 화면을 볼 수 있다.
http://localhost:3000/posts/create
게시글 작성을 확인하면 로그인을 한 사용자만 접근이 가능하게 했기 때문에 로그인이 필요하다.
등록을 하면 목록으로 이동한다.
목록을 클릭하면 이와 같은 상세 페이지를 볼 수 있다.
이제 글을 수정하고 삭제하는 기능이 필요하다.
또한, App.js에서 관리하고 있는 라우터들을 분리해야 할 필요가 있어보인다.
728x90
반응형
'STUDY > Project' 카테고리의 다른 글
게시글 수정, 삭제 기능 추가하기 - postController (0) | 2025.05.07 |
---|---|
프론트엔드 기능별 라우터 분리 - routes/ (0) | 2025.05.06 |
전역 상태 관리하기 - AuthContext, 로그아웃 기능 (0) | 2025.05.05 |
로그인 응답 구조로 인해 발생한 오류 "undefined" is not valid JSON (0) | 2025.05.04 |
인증이 필요한 보호 페이지 - ProtectedRoute, DashboardPage (0) | 2025.05.04 |