# 여러 술이 있는 리뷰 게시글 목록들이 있고, 그 글들의 검색과 페이지네이션을 구현해보려고 한다.
* 구현 목적: 더미데이터에서 데이터를 가져온 다음 페이지네이션과, 검색을 구현해 봤다.
1. headrImg는 다른 조원들이 만든 헤더부분이다.
2. BlogInformation은 게시글 더미데이터를 이다.(json형태로 있는 것을 가져오고 있다.)
추후에 axios 요청을 해서 서버에서 실제 데이터를 가져오는 형식으로 바꿀 것이다.
3. posts 게시글 하나하나를 컴포넌트화 한 컴포넌트 입니다.
4. 페이지네이션시 나오는 번호들을 어떻게 나오게 할지를 컴포넌트화한 부분이다.
[Bolg.js]
import headerImg from '../../assets/img/blog-header.jpg'
import { useState, useEffect } from 'react';
import BlogInformation from './blog-info-json';
import Posts from './Post';
import Pagination from './Pagination';
1. searchTerm은 검색 인풋을 저장할 용도로 쓸 것이다.
2. posts는 게시글 목록들을 저장할 용도로 쓸 것.
3. currentPage는 현재 페이지네이션 위치를 저장하는 용도
4. postsperPage는 한 페이지에 보여줄 최대 갯수를 보여줄 용도를 쓰일 것
[Bolg.js]
const Blog = () => {
const [searchTerm, setSearchTerm] = useState("")
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(6);
const tmp = [];
1. useEffect를 이용하여 searchTerm(검색 인풋을 담는 변수)가 변할 때 마다 게시글을 최신화 할 것이다.
2. BlogInformation에서 데이터를 가져오고 filter를 이용하여 검색을 구현하고자 합니다.
3. searchTern이 비어있으면 전체 값을 가져옵니다
4. searchTerm을 소문자화 한다음 includes를 이용하여 해당 값이 있는 것만 값을 가져옵니다.
5. 가져온 값들을 map을 이용하여 tmp = [] 땜방용 변수에 넣어 줍니다.
6. tmp 값을 setPosts를 이용하여 posts에 저장됩니다.
[Bolg.js]
useEffect(() =>
{
BlogInformation.filter((val) => {
if (searchTerm === "") {
return val;
} else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val;
}
}).map((val, key) => {
return (
tmp.push(val)
);
})
setPosts(tmp);
console.log(posts)
}, [searchTerm])
1. indexOfLast는 현재 페이지위치 * 한 페이지당 게시글 갯수가 됩니다.
=> 즉, 현재 페이지가 2라면, 2 * 6 되므로 이페이지의 마지막 위치 인덱스 값인 12가 되는 것 입니다.
2. indexOfFirst는 현재페이지의 마지막위치 - 한 페이지당 게시글 갯수
=> 마찬가지로 페이지가 2라면 2페이지의 마지막 위치 12 - 6 = 6
=> 2페이지의 마지막 위치가 되는 것
3. currentPost는 앞서 구한 indexOfFirst, indexOfLast를 기준으로 slice해주고, 이젠 이걸 이용해서 페이지네이션했을 때 보여줄 게시글 인덱스 범위 만큼을 보여줄 수 있는 것 입니다.
[Bolg.js]
/* 새로 추가한 부분 - 페이지네이션 목록 길이 계산 */
const indexOfLast = currentPage * postsPerPage;
const indexOfFirst = indexOfLast - postsPerPage;
const currentPosts = (searchTerm) => {
let currentPosts = 0;
currentPosts = posts.slice(indexOfFirst, indexOfLast);
return currentPosts;
};
0. 이제 리턴문 ( jsx ) 안에서 태그 섹션별로 설명하겠습니다
1. 디자인된 html 문서에서 posts-list부분이 게시글을 보여주는 부분입니다. 그렇기 때문에 여기서 게시글을 보여주려고 합니다.
2. posts 컴포넌트를 가져 옵니다. 그럼 post를 살펴 봅시다.
3. 들어가기전에 Posts에 porps값으로 currentPosts(posts)를 입력하면 앞서 본 것처럼 정해진 길이 만큼 슬라이신 된 게시글 목록들이 들어가게 됩니다.
즉, 슬라이신된 포스트 목록들을 props를 전달 해주고 있습니다.
[Bolg.js]
{/* <!-- ======= Blog Section ======= --> */}
<section id="blog" className="blog">
<div className="container aos-init aos-animate" data-aos="fade-up">
<div className="row g-5">
<div
className="col-lg-8 aos-init aos-animate"
data-aos="fade-up"
data-aos-delay="200"
>
<div className="row gy-5 posts-list">
{/* 게시글 하나하나가 start 지점 */}
{/* {console.log(posts)} */}
<Posts posts={currentPosts(posts)} />
</div>
{/* <!-- End blog posts list --> */}
+
1. props로 받아온 posts 목록들을 비구조화 할당을 합니다.
2. 각각에 보여줄 위치에 넣어줍니다.
[Post.js]
import React from "react";
const Posts = ({ posts }) => {
return (
<>
{posts.map((post) => (
<div className="col-lg-6">
<article className="d-flex flex-column">
<div className="post-img">
<img src={post.img} alt="" className="img-fluid" />
</div>
<h2 className="title">
<a href="blog-details.html">{post.title}</a>
</h2>
<div className="meta-top">
<ul>
<li className="d-flex align-items-center">
<i className="bi bi-person"></i>{" "}
<a href="blog-details.html">{post.name}</a>
</li>
<li className="d-flex align-items-center">
<i className="bi bi-clock"></i>{" "}
<a href="blog-details.html">
<time dateTime="2022-01-01">{post.time}</time>
</a>
</li>
<li className="d-flex align-items-center">
<i className="bi bi-chat-dots"></i>{" "}
<a href="blog-details.html">{post.comments}</a>
</li>
</ul>
</div>
<div className="content">
<p>{post.content}</p>
</div>
<div className="read-more mt-auto align-self-end">
<a href="blog-details.html">
Read More <i className="bi bi-arrow-right"></i>
</a>
</div>
</article>
</div>
))}
</>
);
};
export default Posts;
1. 게시글들의 범위에 따라서 페이지네이션도 이동해야 합니다.
2. 페이지네이션 컴포넌트에 props값으로 slice된 게시글 목록범위와
게시글들의 전체길이
현재위치를 prorp로 전달해줍니다.
[Bolg.js]
{/* 페이지네이션 하는 부분 */}
<div className="blog-pagination">
<ul className="justify-content-center">
<Pagination
postsPerPage={postsPerPage}
totalPosts={posts.length}
paginate={setCurrentPage}
></Pagination>
</ul>
</div>
{/* <!-- End blog pagination --> */}
+
1. props값으로 받아온 값들은 slice된 게시글 목록범위와 게시글들의 전체길이, 현재위치 입니다.
2. 전체 길이(totalPosts)에서 한 게시물당 길이 즉, slice된 게시글 목록들의 범위를 나눈 페이지네이션의 총 숫자 크기가 나옵니다.
페이지네이션 하나당 게시글을 6개로 설정하고, 총 게시글 수가 30개 이면 5개(30 / 6 )의 페이지네이션이 나오는 것.
3. 그 숫자들을 배열에 담습니다.
4. 숫자 목록들(PageNumbers)을 map이용해 html로 표현해주고 각각의 번호태그(li)가 클릭되면 paginate(number)를 즉시 실행 함수로 실행 해줍니다.
5. paginate는 propos로 받은 setCurrentPage라는 현재 페이지 위치를 저장하는 useState 값이고, 해당 값이 변하는 순간 React에서는 리랜더링을 해주고, 아까 보았던 페이지네이션 목록을 계산하는 부분이 다시 실행되서 그에 맞게 다시 slice해서 보여주는 작업을 해주는 것 입니다.
[Pagenation.js]
const Pagination = ({ postsPerPage, totalPosts, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++) {
pageNumbers.push(i);
}
return (
<>
<div className="blog-pagination">
<ul className="justify-content-center">
{pageNumbers.map((number) => (
<li key={number}>
<a onClick={() => paginate(number)} className="active">{ number }</a>
</li>
))}
</ul>
</div>
</>
);
};
export default Pagination;
1. 처음 useEffect를 이용해서 게시글을 목록들을 가져오던 것과 원리는 같습니다.
2. 여기선 검색 인풋의 onChange를 받아서 searchTerm을 저장해줍니다. 아까 searchTerm을 기준으로 return val 해준게 기억 나시나요? 여기 인풋에 저장되는 searchTerm을 가져왔던 것 입니다.
3. 여기는 검색할 때 검색목록의 단어들만 map으로 보여주는 식으로 하고 있습니다.
[Bolg.js]
{/* 검색 인풋 위치 */}
<div className="sidebar-item search-form">
<h3 className="sidebar-title">Search</h3>
<form action="" className="mt-3">
<input type="text" id="search" onChange={e => {setSearchTerm(e.target.value)}} />
{BlogInformation.filter((val) => {
// console.log(val);
if (searchTerm === "") {
return ""
} else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val
}
})
.map((val, key) => {
return (
<div>
<p>{val.name}</p>
</div>
);
})
}
[ Blog.js ] - 전체 코드
import headerImg from '../../assets/img/blog-header.jpg'
import { useState, useEffect } from 'react';
import BlogInformation from './blog-info-json';
import Posts from './Post';
import Pagination from './Pagination';
const Blog = () => {
const [searchTerm, setSearchTerm] = useState("")
const [posts, setPosts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(6);
const tmp = [];
// const blogHandler = (e) => {
// e.preventDefault();
// const search = document.getElementById("#search");
// console.log(search);
// setSearchTerm(search)
// }
useEffect(() =>
{
BlogInformation.filter((val) => {
if (searchTerm === "") {
return val;
} else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val;
}
}).map((val, key) => {
return (
tmp.push(val)
);
})
setPosts(tmp);
console.log(posts)
}, [searchTerm])
/* 새로 추가한 부분 - 페이지네이션 목록 길이 계산 */
const indexOfLast = currentPage * postsPerPage;
const indexOfFirst = indexOfLast - postsPerPage;
const currentPosts = (posts) => {
let currentPosts = 0;
currentPosts = posts.slice(indexOfFirst, indexOfLast);
return currentPosts;
};
return (
<>
<main id="main">
{/* <!-- ======= Breadcrumbs ======= --> */}
<div
className="breadcrumbs d-flex align-items-center"
style={{ backgroundImage: `url(${headerImg})` }}
>
<div className="container position-relative d-flex flex-column align-items-center">
<h2>Blog</h2>
<ol>
<li>
<a href="/">Home</a>
</li>
<li>Blog</li>
</ol>
</div>
</div>
{/* <!-- End Breadcrumbs --> */}
{/* <!-- ======= Blog Section ======= --> */}
<section id="blog" className="blog">
<div className="container aos-init aos-animate" data-aos="fade-up">
<div className="row g-5">
<div
className="col-lg-8 aos-init aos-animate"
data-aos="fade-up"
data-aos-delay="200"
>
<div className="row gy-5 posts-list">
{/* 게시글 하나하나가 start 지점 */}
{/* {console.log(posts)} */}
<Posts posts={currentPosts(posts)} />
</div>
{/* <!-- End blog posts list --> */}
{/* 페이지네이션 하는 부분 */}
<div className="blog-pagination">
<ul className="justify-content-center">
<Pagination
postsPerPage={postsPerPage}
totalPosts={posts.length}
paginate={setCurrentPage}
></Pagination>
</ul>
</div>
{/* <!-- End blog pagination --> */}
</div>
<div
className="col-lg-4 aos-init aos-animate"
data-aos="fade-up"
data-aos-delay="400"
>
<div className="sidebar ps-lg-4">
{/* 검색 인풋 위치 */}
<div className="sidebar-item search-form">
<h3 className="sidebar-title">Search</h3>
<form action="" className="mt-3">
<input type="text" id="search" onChange={e => {setSearchTerm(e.target.value)}} />
{BlogInformation.filter((val) => {
// console.log(val);
if (searchTerm === "") {
return ""
} else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())) {
return val
}
})
.map((val, key) => {
return (
<div>
<p>{val.name}</p>
</div>
);
})
}
<button
type="button"
// onSubmit={blogHandler}
>
<i className="bi bi-search"></i>
</button>
</form>
</div>
{/* <!-- End sidebar search formn--> */}
<div className="sidebar-item categories">
<h3 className="sidebar-title">Categories</h3>
<ul className="mt-3">
<li>
<a href="!#">
General <span>(25)</span>
</a>
</li>
<li>
<a href="!#">
Lifestyle <span>(12)</span>
</a>
</li>
<li>
<a href="!#">
Travel <span>(5)</span>
</a>
</li>
<li>
<a href="!#">
Design <span>(22)</span>
</a>
</li>
<li>
<a href="!#">
Creative <span>(8)</span>
</a>
</li>
<li>
<a href="!#">
Educaion <span>(14)</span>
</a>
</li>
</ul>
</div>
{/* <!-- End sidebar categories--> */}
<div className="sidebar-item recent-posts">
<h3 className="sidebar-title">Recent Posts</h3>
<div className="mt-3">
<div className="post-item mt-3">
<img
src="assets/img/blog/blog-recent-1.jpg"
alt=""
className="flex-shrink-0"
/>
<div>
<h4>
<a href="blog-post.html">
Nihil blanditiis at in nihil autem
</a>
</h4>
<time dateTime="2020-01-01">Jan 1, 2020</time>
</div>
</div>
{/* <!-- End recent post item--> */}
<div className="post-item">
<img
src="assets/img/blog/blog-recent-2.jpg"
alt=""
className="flex-shrink-0"
/>
<div>
<h4>
<a href="blog-post.html">Quidem autem et impedit</a>
</h4>
<time dateTime="2020-01-01">Jan 1, 2020</time>
</div>
</div>
{/* <!-- End recent post item--> */}
<div className="post-item">
<img
src="assets/img/blog/blog-recent-3.jpg"
alt=""
className="flex-shrink-0"
/>
<div>
<h4>
<a href="blog-post.html">
Id quia et et ut maxime similique occaecati ut
</a>
</h4>
<time dateTime="2020-01-01">Jan 1, 2020</time>
</div>
</div>
{/* <!-- End recent post item--> */}
<div className="post-item">
<img
src="assets/img/blog/blog-recent-4.jpg"
alt=""
className="flex-shrink-0"
/>
<div>
<h4>
<a href="blog-post.html">
Laborum corporis quo dara net para
</a>
</h4>
<time dateTime="2020-01-01">Jan 1, 2020</time>
</div>
</div>
{/* <!-- End recent post item--> */}
<div className="post-item">
<img
src="assets/img/blog/blog-recent-5.jpg"
alt=""
className="flex-shrink-0"
/>
<div>
<h4>
<a href="blog-post.html">
Et dolores corrupti quae illo quod dolor
</a>
</h4>
<time dateTime="2020-01-01">Jan 1, 2020</time>
</div>
</div>
{/* <!-- End recent post item--> */}
</div>
</div>
{/* <!-- End sidebar recent posts--> */}
<div className="sidebar-item tags">
<h3 className="sidebar-title">Tags</h3>
<ul className="mt-3">
<li>
<a href="!#">App</a>
</li>
<li>
<a href="!#">IT</a>
</li>
<li>
<a href="!#">Business</a>
</li>
<li>
<a href="!#">Mac</a>
</li>
<li>
<a href="!#">Design</a>
</li>
<li>
<a href="!#">Office</a>
</li>
<li>
<a href="!#">Creative</a>
</li>
<li>
<a href="!#">Studio</a>
</li>
<li>
<a href="!#">Smart</a>
</li>
<li>
<a href="!#">Tips</a>
</li>
<li>
<a href="!#">Marketing</a>
</li>
</ul>
</div>
{/* <!-- End sidebar tags-->. */}
</div>
{/* <!-- End Blog Sidebar --> */}
</div>
</div>
</div>
</section>
{/* <!-- End Blog Section --> */}
</main>
</>
);
};
export default Blog;
후기
어찌어찌 페이지네이션과, 검색을 구현하는데 성공했습니다.
이제는 상세 페이지와 검색을 할 때 onChange로 구현하는게 아니라 버튼을 클릭햇을 때 하게 하는 구현
그리고 댓글 기능을 구현을 목표로 하고 있습니다.
'React 실습' 카테고리의 다른 글
[Githib Action]CI / CD 자동 배포 정리 (질 & 답 형식) (0) | 2023.05.12 |
---|---|
리액트 커스텀 훅 만들기 (0) | 2022.11.22 |
yoga 프로젝트 작업일지 - 5 (0) | 2022.10.27 |
yoga 프로젝트 작업일지 - 4 (0) | 2022.10.26 |
yoga 프로젝트 작업일지 - 3 (0) | 2022.10.25 |