<div className='form-check mb-3'>
<input className='form-check-input'
type='checkbox'
checked={publish}
onChange={onChangePublish} />
<label className='form-check-label'>
Publish
</label>
</div>
publish
state 값이 true/false 일 때 check 표시/비표시onChangePublish
함수 작성const onChangePublish = (event) => {
setPublish(event.target.checked)
}
onSubmit
함수 수정const onSubmit = () => {
if (editing) {
axios.patch(`http://localhost:3001/posts/${id}`, {
title: title,
body: body,
publish: publish
}) .then(() => {
navigate(`/blogs/${id}`)
})
} else {
axios.post('http://localhost:3001/posts', {
title: title,
body: body,
publish: publish,
createdAt: Date.now()
}).then(() => {
navigate('/blogs')
})
}
}
const {id} = useParams();
useEffect(() => {
if (editing) {
axios.get(`http://localhost:3001/posts/${id}`)
.then((response) => {
// console.log(response.data)
setTitle(response.data.title)
setOriginalTitle(response.data.title)
setBody(response.data.body)
setOriginalBody(response.data.body)
setPublish(response.data.publish)
setOriginalPublish(response.data.publish)
})
}
}, [id, editing])
const isEdited = () => {
return title !== originalTitle || body !== originalBody || publish !== originalPublish
}
src/componenets/BlogForm.jsx
import React from 'react'
import { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import { bool } from 'prop-types';
// Edit Page
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
function BlogForm({editing}) {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const navigate = useNavigate();
const [originalTitle, setOriginalTitle] = useState('');
const [originalBody, setOriginalBody] = useState('');
const [originalPublish, setOriginalPublish] = useState('');
// 공개 여부 결정
const [publish, setPublish] = useState(false);
// Edit Page
const {id} = useParams();
useEffect(() => {
if (editing) {
axios.get(`http://localhost:3001/posts/${id}`)
.then((response) => {
// console.log(response.data)
setTitle(response.data.title)
setOriginalTitle(response.data.title)
setBody(response.data.body)
setOriginalBody(response.data.body)
setPublish(response.data.publish)
setOriginalPublish(response.data.publish)
})
}
}, [id, editing])
const isEdited = () => {
return title !== originalTitle || body !== originalBody || publish !== originalPublish
}
const onSubmit = () => {
if (editing) {
axios.patch(`http://localhost:3001/posts/${id}`, {
title: title,
body: body,
publish: publish
}) .then(() => {
navigate(`/blogs/${id}`)
})
} else {
axios.post('http://localhost:3001/posts', {
title: title,
body: body,
publish: publish,
createdAt: Date.now()
}).then(() => {
navigate('/blogs')
})
}
}
const goBack = () => {
if (editing) {
navigate(`/blogs/${id}`)
} else {
navigate('/blogs/')
}
}
const onChangePublish = (event) => {
setPublish(event.target.checked)
}
return (
<div>
<h1>{editing ? 'Edit' : 'Create'} a blog post</h1>
<div className='mb-3'>
<label className='form-label'>Title</label>
<input
className='form-control'
value={title}
onChange={(event) => {
setTitle(event.target.value)
}}
/>
</div>
<div className='mb-3'>
<label className='form-label'>Body</label>
<textarea
className='form-control'
value={body}
onChange={(event) => {
setBody(event.target.value)
}}
rows={10}
/>
</div>
{/* 공개 여부 결정하기 */}
<div className='form-check mb-3'>
<input className='form-check-input'
type='checkbox'
checked={publish}
onChange={onChangePublish} />
<label className='form-check-label'>
Publish
</label>
</div>
<button
className='btn btn-primary'
onClick={onSubmit}
disabled={editing && !isEdited()}
>{editing ? 'Edit' : 'Post'}</button>
<button
className='btn btn-danger ms-3'
onClick={goBack}
>Cancel</button>
</div>
)
}
BlogForm.propTypes = {
editing: bool
}
BlogForm.defaultProps = {
editing: false
}
export default BlogForm
renderBlogList
함수를 filter 함수를 사용하여 publish가 true 값인 post만 출력하도록 수정const renderBlogList = () => {
if (loading) {
return (
<LoadingSpinner/>
)
}
if (posts.length === 0) {
return (<div>'No blog posts found'</div>)
}
// publish가 true 인 post만 filter
return posts.filter(post=> {
return post.publish
}).map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate(`/blogs/${post.id}`)}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})
}
비공개 글을 포함한 전체 글을 조회할 수 있는 Admin 페이지를 만들어보자.
routes.js
...
import AdminPage from './pages/AdminPage';
const routes = [
...
{
path:'/admin',
element: <AdminPage />
},
...
]
export default routes;
ListPage.jsx
에 renderBlogList
라는 함수를 통해 게시글 리스트를 출력했었는데, 이 부분을 따로 빼서 BlogList.jsx
라는 함수 컴포넌트를 만든다.isAdmin
이라는 boolean 값을 props로 갖게 하고, 기본 값은 false로 정한다.isAdmin
값이 true 일 경우에만 delete 버튼이 출력되도록 수정isAdmin
이 true일 경우에는 모든 post를, false 일 경우에는 publish 가 true 인 post를 반환하도록 함수 수정src/components/BlogList.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import LoadingSpinner from '../components/LoadingSpinner';
import { Link, useNavigate } from 'react-router-dom';
import { bool } from 'prop-types';
function BlogList({isAdmin}) {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
const [loading, setLoading] = useState(true);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
// post 불러온 이후에 로딩중 상태 false로 변경
setLoading(false);
})
}
const deleteBlog = (event, id) => {
// 이벤트 버블링 막기
event.stopPropagation();
axios.delete(`http://localhost:3001/posts/${id}`)
// 삭제하려는 id와 같지 않은 post만 담아 posts 업데이트
.then(() => {
setPosts(prevPosts => {
return prevPosts.filter(post => {
return post.id !== id;
})
})
})
}
useEffect(() => {
getPosts();
}, [])
if (loading) {
return (
<LoadingSpinner/>
)
}
if (posts.length === 0) {
return (<div>'No blog posts found'</div>)
}
// ************** filter 조건 수정
return posts.filter(post=> {
return isAdmin || post.publish
}).map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate(`/blogs/${post.id}`)}>
// ************** isAdmin 일 경우에만 delete 버튼 출력
{isAdmin ? <button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button> : null}
</Card>
)
})
}
// ************** isAdmin props 타입 지정
BlogList.propTypes = {
isAdmin: bool
}
BlogList.defaultProps = {
isAdmin: false
}
export default BlogList
AdminPage
는 BlogList
에 props로 true를, ListPage
는 BlogList
에 props로 false를 전달하도록 수정src/pages/AdminPage.jsx
import React from 'react'
import BlogList from '../components/BlogList.jsx';
import { Link } from 'react-router-dom';
function AdminPage() {
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Admin</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
<BlogList isAdmin={true}/>
</div>
)
}
export default AdminPage
src/pages/ListPage.jsx
import React from 'react'
import BlogList from '../components/BlogList.jsx';
function ListPage() {
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
</div>
<BlogList/>
</div>
)
}
export default ListPage