24/1/29 pagenation (firebase)
2024. 1. 30. 08:36ㆍ카테고리 없음
<ListPage.jsx> pagenation을 파이어베이스를 이용해 구현해 보았다.
"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import writeImage from "@/app/assets/images/icon/write_icon.png";
import userIcon from "../assets/images/icon/userIcon.png";
import { useQuery } from "@tanstack/react-query";
import { collection, getDocs, limit, orderBy, query, startAfter } from "firebase/firestore"; //여기서 새롭게 써본
import { db, storage } from "@/shared/firebase"; //파이어베이스 메서드가 많다.
import { Post } from "../assets/types/types";
import { nanoid } from "nanoid";
import Link from "next/link";
import CategoryBtn from "@/components/community/CategoryBtn";
import { useRouter } from "next/navigation";
import CuteHeart from "@/components/community/CuteHeart";
import { getDownloadURL, ref } from "firebase/storage";
import PostCard from "@/components/mypage/PostCard";
import BestPost from "@/components/community/BestPost";
import { getHearts } from "@/components/community/Fns";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/config/configStore";
import moeumLoading from "../assets/images/moeumLoading.gif";
export default function ListPage() {
const [newPost, setNewPost] = useState<Post>({ //게시글 담아둘 NewPost
id: "",
uid: "",
title: "",
content: "",
profile: "",
nickname: "",
createdAt: 0,
category: ""
});
const [top3Shops, setTop3Shops] = useState<(Post | undefined)[]>(); //페이지 네이션과 무관한
const router = useRouter(); //베스트글 3개 뽑기
const { data: posts, isLoading } = useQuery({
queryKey: ["posts"], //이 유즈쿼리는 단순히 모든 게시글을 불러온다.
queryFn: () => {
const getPosts = async () => {
let data: Post[] = [];
const response = await getDocs(collection(db, "posts"));
response.forEach((post) => {
const postData = post.data();
data.push({ ...postData, id: post.id });
});
// setPostArray(data);
return data;
};
return getPosts();
}
});
const [postsArray, setPostArray] = useState<Post[] | undefined>(posts); //일단 초기값을 유즈쿼리로 가져온
const { data: hearts } = useQuery({ //값을 초기값으로 달아봤는데 작동안한다.
queryKey: [`hearts`], //역시 setPostArray로 직접 바꿔주지 않는한
queryFn: getHearts //화면 변경은 안된다.
});
const filteredPosts = postsArray //조건부로 화면을 보여주기위한 필터링
?.filter((post) => {
if (newPost.category === "" || newPost.category === "전체모음") {
return true;
}
return newPost.category === post.category;
})
.sort((a, b) => {
if (a.createdAt && b.createdAt) {
return b.createdAt - a.createdAt; //속성에 있는 createdAt으로 내림차순
}
return 1;
})
.slice(0, 5);
useEffect(() => {
const uniqueArray = hearts?.map((item) => { //페이지네이션과는 무관한로직
return item.postId;
}); //설명하자면 item의 포스트아이디를 갖는 배열을 새로 만들고
// console.log(uniqueArray, "요거는?");
const countThumbs: Record<string, number> = {}; //문자열또는 숫자를 요소로 갖는 객체
uniqueArray?.forEach((element) => {
const key = String(element);
countThumbs[key] = (countThumbs[key] || 0) + 1; //이제 위에 있던 배열을 돌며 해당배열의 요소를
}); //키로 갖고 동일한 키에대한 count를 value로 갖는 객체 생성
const entries = Object.entries(countThumbs); //그 키와 밸류 2개를 요소로갖는 배열을 가진
const sortedEntries = entries.sort((a, b) => b[1] - a[1]); //객체를 생성해서 count의 값에따른
const top3 = sortedEntries.slice(0, 3); // 내림차순으로 만든다. 그리고 앞에서 3개를 뽑는다.
// console.log(top3, "탑3");
const foundTop3 = top3.map((postId) => { //이제 top3에 해당하는 post를 찾아낸다.
return posts?.find((post) => {
return post.id === postId[0];
});
});
// console.log(foundTop3, "과연");
if (foundTop3 !== undefined) {
setTop3Shops(foundTop3); //그 찾아낸 top3의 post를 useState에 저장 //참고로 페이지네이션과는무관
}
}, [hearts]);
let BtnArray: number[] = []; //페이지네이션을 위한 페이지 이동버튼 생성 로직
if (posts?.length) {
for (let i = 1; i <= Math.ceil(posts?.length / 5); i++) { //페이지에 게시글을 5개씩 담을 예정
BtnArray.push(i); //전체 posts의 길이를 5개로 나눈값의 소수첫번쨰에서 올림한값이
} //필요한 페이지의 갯수이다. 그숫자들을 갖는 배열이 BTNArray
// console.log(BtnArray, "어레이");
}
const pagenationHandler = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
const BtnNumber = e.currentTarget.innerText; //여기는 페이지버튼을 눌렀을때 동작하는 로직
if (BtnNumber === "1") { //1번 버튼을 누르면
let data2: Post[] = [];
const postRef = collection(db, "posts"); //단순히 createdAt의 내림차순으로
const postQuery = query(
postRef,
orderBy("createdAt", "desc"),
limit(5) //가장앞에 5개의 데이터를 불러온다.
);
const snapshot = await getDocs(postQuery);
snapshot.forEach((doc) => {
data2.push({ ...doc.data(), id: doc.id }); //거기다 아이디 달아주고
});
setPostArray(data2); //state에 저장하면 끝
} else {
let data2: Post[] = []; // 1번 이외의 버튼
const first = query(collection(db, "posts"), orderBy("createdAt", "desc"), limit(5 * (Number(BtnNumber) - 1)));
const documentSnapshots = await getDocs(first); //상대적 앞페이지들에대한 정보
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
//앞페이지들에서 가장 끝에 Post정보 const postRef = collection(db, "posts");
const postQuery = query(postRef, orderBy("createdAt", "desc"), startAfter(lastVisible), limit(5));
const snapshot = await getDocs(postQuery); //startAfter로 가장 끝 정보부터 뒤에 5개를 가져옴
snapshot.forEach((doc) => {
data2.push({ ...doc.data(), id: doc.id });
});
setPostArray(data2); //그걸 화면에 뿌림
}
};
if (isLoading)
return (
<div className="flex justify-center items-center w-full h-full">
<Image src={moeumLoading} alt="loading" className="w-[300px] h-[300px]" />
</div>
);
return (
<>
{/* 전체 컨테이너 */}
<div className="flex flex-col items-center w-full gap-[20px]">
{/* 글 모음/ 인기순위 컨텐츠 */}
<div className="flex w-full py-[60px] flex-col justify-center items-center gap-[60px] bg-[#FAFAFA]">
<div className="flex flex-col gap-[12px] items-center">
<section>
<h1 className="text-[28px] text-[#212121] font-semibold leading-[36px]">이 달의 BEST 게시글 모-음</h1>
<h2 className="text-[18px] text-[#5C5C5C] leading-[26px]">가장 인기 많았던 게시글을 확인해보세요!</h2>
</section>
</div>
{/* 인기순위 컨테이너 */}
<div className="flex justify-center items-center gap-[24px] cursor-pointer max-sm:overflow-x-scroll max-sm:w-[350px] max-sm:justify-start max-sm:scrollbar-hide max-md:overflow-x-scroll max-md:w-[350px] max-md:justify-start max-lg:overflow-x-scroll max-lg:w-[680px] max-lg:justify-start">
{top3Shops?.map((post) => {
return <React.Fragment key={nanoid()}>{post && <BestPost post={post} />}</React.Fragment>;
})}
</div>
</div>
<div className="flex flex-col justify-center items-center gap-[60px] w-full pt-[60px] px-[20px]">
<div className="">
<div className="flex flex-col justify-center items-center gap-[40px]">
<h2 className="text-[28px] text-[#212121] text-center font-semibold leading-[36px]">실시간모음</h2>
<section className="grid max-sm:grid-cols-3 sm:grid-cols-5 gap-[16px] w-full">
<CategoryBtn text="전체모음" type="" setNewPost={setNewPost} newPost={newPost} />
<CategoryBtn text="일상이야기" type="" setNewPost={setNewPost} newPost={newPost} />
<CategoryBtn text="맛집추천" type="" setNewPost={setNewPost} newPost={newPost} />
<CategoryBtn text="취미생활" type="" setNewPost={setNewPost} newPost={newPost} />
<CategoryBtn text="문의하기" type="" setNewPost={setNewPost} newPost={newPost} />
</section>
</div>
</div>
<div className="w-full flex justify-center">
<div className="w-[680px] flex justify-end">
<Link href="community/write">
<button className=" flex h-[40px] px-[12px] py-[8px] justify-center items-center gap-[8px] rounded-[8px] text-[white] bg-[#FF8145] hover:bg-[#E5743E] ">
<div className="w-[20px] h-[20px]">
<Image src={writeImage} alt="write"></Image>
</div>
<p className="text-[14px] leading-[20px] font-medium ">작성하기</p>
</button>
</Link>
</div>
</div>
<div className="flex w-full flex-col items-center gap-[40px]">
{/* 작성하기 버튼 */}
{/* 게시글 전체 컨테이너 */}
<div className="flex flex-col justify-center items-center w-full">
{filteredPosts?.map((post) => {
return (
<React.Fragment key={nanoid()}>
<PostCard post={post} />
</React.Fragment>
);
})}
</div>
</div>
</div>
<section className="flex gap-1 ">
{BtnArray.map((item) => {
return (
<button
key={nanoid()}
className="mb-[60px] flex h-[40px] px-[12px] py-[8px] justify-center items-center gap-[8px] rounded-[8px] text-[white] bg-[#FF8145] hover:bg-[#E5743E] "
onClick={(e) => pagenationHandler(e)}
>
{item}
</button>
);
})}
</section>
</div>
</>
);
}
//처음 해본 pagenation이라 이게 맞는지도 모르겠다. 개선안 적어주시면 감사합니다.