DB에 있는 데이터를 불러와서 ListPage에 출력하는 것을 구현해보자.
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
function ListPage() {
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
console.log(response);
})
}
getPosts();
return (
<div>ListPage</div>
)
}
export default ListPage
src/components/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>ListPage</div>
)
}
export default ListPage
src/components/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<h1>Blogs</h1>
{posts.map(post => {
return (
<div key={post.id}>{post.title}</div>
)
})}
</div>
)
}
export default ListPage
src/components/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<h1>Blogs</h1>
{posts.map(post => {
return (
<div className="card mb-3" key={post.id}>
<div className="card-body">
{post.title}
</div>
</div>
)
})}
</div>
)
}
export default ListPage
src/components/Card.jsx
import React from 'react'
function Card(props) {
return (
<div className="card mb-3">
<div className="card-body">
{props.title}
</div>
</div>
)
}
export default Card
import React from 'react'
function Card({title}) {
return (
<div className="card mb-3">
<div className="card-body">
{title}
</div>
</div>
)
}
export default Card
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
// Card 컴포넌트 불러오기
import Card from '../components/Card';
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<h1>Blogs</h1>
{posts.map(post => {
return (
// <div className="card mb-3" key={post.id}>
// <div className="card-body">
// {post.title}
// </div>
// </div>
<Card key={post.id} title={post.title} />
)
})}
</div>
)
}
export default ListPage
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<h1>Blogs</h1>
{posts.map(post => {
return (
<Card key={post.id} title={post.title}>
<div className='d-flex justify-content-between'>
<div>{post.title}</div>
<div>buttons</div>
</div>
</Card>
)
})}
</div>
)
}
export default ListPage
src/components/Card.jsx
import React from 'react'
function Card({title, children}) {
return (
<div className="card mb-3">
<div className="card-body">
{children}
</div>
</div>
)
}
export default Card
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<h1>Blogs</h1>
{posts.map(post => {
return (
<Card key={post.id} title={post.title}>
<button>button</button>
</Card>
)
})}
</div>
)
}
export default ListPage
src/components/Card.jsx
import React from 'react'
function Card({title, children}) {
return (
<div className="card mb-3">
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
export default Card
npm install --save prop-types
src/components/Card.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Card({title, children}) {
return (
<div className="card mb-3">
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
Card.propTypes = {
title: PropTypes.string,
}
export default Card
src/components/Card.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Card({title, children}) {
return (
<div className="card mb-3">
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
Card.propTypes = {
title: PropTypes.string,
}
Card.defaultProps = {
title: 'Title'
}
export default Card
src/components/Card.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Card({title, children}) {
return (
<div className="card mb-3">
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
Card.propTypes = {
title: PropTypes.string.isRequired,
}
// Card.defaultProps = {
// title: 'Title'
// }
export default Card
children: PropTypes.element
로 children 타입을 지정하면 children에는 한 개의 element만 올 수 있음<Link>
컴포넌트 사용src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
// Link 컴포넌트 불러오기
import { Link } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
// ********* 생성페이지로 이동하는 버튼
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
{posts.map(post => {
return (
<Card key={post.id} title={post.title}>
<button>button</button>
</Card>
)
})}
</div>
)
}
export default ListPage
src/components/Card.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Card({title, onClick, children}) {
return (
<div className="card mb-3" onClick={onClick}>
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
Card.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.element,
onClick: PropTypes.func
}
Card.defaultProps = {
children: null,
onClick: () => {}
}
export default Card
src/pages/ListPage.jsx
useNavigate
훅을 이용하여 수정 페이지로 이동하는 onClick 함수 작성import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
{posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button>button</button>
</Card>
)
})}
</div>
)
}
export default ListPage
src/index.css
.cursor-pointer {
cursor: pointer;
}
src/components/Card.jsx
import React from 'react'
import PropTypes from 'prop-types'
function Card({title, onClick, children}) {
return (
<div className="card mb-3 cursor-pointer" onClick={onClick}>
<div className="card-body">
<div className='d-flex justify-content-between'>
<div>{title}</div>
{/* children이 있을 경우에만 출력 */}
{children && <div>{children}</div>}
</div>
</div>
</div>
)
}
Card.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.element,
onClick: PropTypes.func
}
Card.defaultProps = {
children: null,
onClick: () => {}
}
export default Card
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
const deleteBlog = (event, id) => {
// 이벤트 버블링 막기
event.stopPropagation();
axios.delete(`http://localhost:3001/posts/${id}`)
}
useEffect(() => {
getPosts();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
{posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})}
</div>
)
}
export default ListPage
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
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();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
{posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})}
</div>
)
}
export default ListPage
src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
const getPosts = () => {
axios.get('http://localhost:3001/posts')
.then((response) => {
setPosts(response.data);
})
}
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();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
// ****************************
{posts.length > 0 ? posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
}) : 'No blog posts found'}
</div>
)
}
export default ListPage
Spinner
컴포넌트 사용src/pages/ListPage.jsx
import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
const [posts, setPosts] = useState([]);
const navigate = useNavigate();
// ******** 로딩중 상태를 저장할 state
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();
}, [])
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
// ******** 로딩중일 경우에는 spinner 출력
{ loading ? (
<div className="d-flex justify-content-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : posts.length > 0 ? posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
}) : 'No blog posts found'}
</div>
)
}
export default ListPage
const renderBlogList = () => {
// 로딩중일 경우 loading Spinner 출력
if (loading) {
return (
<div className="d-flex justify-content-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
// post가 없을 경우 메시지 출력
if (posts.length === 0) {
return (<div>'No blog posts found'</div>)
}
// 나머지의 경우 post 출력
return posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})
}
ListPage.jsx
수정import React from 'react'
import axios from 'axios'
import { useState, useEffect } from 'react'
import Card from '../components/Card';
import { Link, useNavigate } from 'react-router-dom';
function ListPage() {
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();
}, [])
const renderBlogList = () => {
if (loading) {
return (
<div className="d-flex justify-content-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
if (posts.length === 0) {
return (<div>'No blog posts found'</div>)
}
return posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})
}
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
// ****************************
{renderBlogList()}
</div>
)
}
export default ListPage
src/componenets/LoadingSpinner.jsx
import React from 'react'
function LoadingSpinner() {
return (
<div className="d-flex justify-content-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
export default LoadingSpinner
ListPage.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';
function ListPage() {
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();
}, [])
const renderBlogList = () => {
if (loading) {
return (
// ****************************
<LoadingSpinner/>
)
}
if (posts.length === 0) {
return (<div>'No blog posts found'</div>)
}
return posts.map(post => {
return (
<Card
key={post.id}
title={post.title}
onClick={() => navigate("/blogs/edit")}>
<button
className="btn btn-danger btn-sm"
onClick={(event) => deleteBlog(event, post.id)}>
Delete</button>
</Card>
)
})
}
return (
<div>
<div className='d-flex justify-content-between'>
<h1>Blogs</h1>
<div>
<Link to="/blogs/create" className='btn btn-success'>Creat New</Link>
</div>
</div>
{renderBlogList()}
</div>
)
}
export default ListPage
useNavigate
훅 사용하여 create후 ListPage로 이동하도록 수정src/components/BlogForm.jsx
import React from 'react'
import { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
function BlogForm() {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
const navigate = useNavigate();
const onSubmit = () => {
axios.post('http://localhost:3001/posts', {
title: title,
body: body
}).then(() => {
// ******* create 후 list 페이지로 이동
navigate('/blogs')
})
}
return (
<div>
<h1>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={20}
/>
</div>
<button
className='btn btn-primary'
onClick={onSubmit}
>Post</button>
</div>
)
}
export default BlogForm