From f20bd2dd510bfa8b2309a9be2dd6ecc6176563ec Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Sun, 11 Aug 2024 22:55:11 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=EC=A7=80=EC=82=AC=ED=95=AD=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IntersectionArea/IntersectionObserver.jsx | 33 +++++++++++++++++++ .../src/components/IntersectionArea/index.js | 1 + frontend/src/constants.js | 1 + frontend/src/hooks/api/useNotices.js | 19 +++++++---- .../LearningLectureDetailPage.jsx | 2 +- .../pages/NoticeListPage/NoticeListPage.jsx | 6 ++-- 6 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/IntersectionArea/IntersectionObserver.jsx create mode 100644 frontend/src/components/IntersectionArea/index.js diff --git a/frontend/src/components/IntersectionArea/IntersectionObserver.jsx b/frontend/src/components/IntersectionArea/IntersectionObserver.jsx new file mode 100644 index 0000000..ec9a682 --- /dev/null +++ b/frontend/src/components/IntersectionArea/IntersectionObserver.jsx @@ -0,0 +1,33 @@ +import { useEffect, useRef } from 'react'; + +export default function IntersectionArea({ onObserve, once = false }) { + const ref = useRef(null); + + useEffect(() => { + const callback = (entries, observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + onObserve(); + if (once) { + observer.disconnect(); + } + } + }); + }; + const observer = new IntersectionObserver(callback, { + root: null, + rootMargin: '0px', + threshold: 0.5, + }); + + if (ref.current) { + observer.observe(ref.current); + } + + return () => { + observer.disconnect(); + }; + }, [onObserve, once]); + + return
; +} diff --git a/frontend/src/components/IntersectionArea/index.js b/frontend/src/components/IntersectionArea/index.js new file mode 100644 index 0000000..055ea95 --- /dev/null +++ b/frontend/src/components/IntersectionArea/index.js @@ -0,0 +1 @@ +export { default as IntersectionArea } from './IntersectionArea'; diff --git a/frontend/src/constants.js b/frontend/src/constants.js index a0aa684..5d1e496 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -2,3 +2,4 @@ export const API_URL = import.meta.env.VITE_API_URL; export const ROOM_URL = import.meta.env.VITE_ROOM_URL; export const CHAT_URL = import.meta.env.VITE_CHAT_URL; export const STATIC_URL = import.meta.env.VITE_STATIC_URL; +export const PAGE_SIZE = 20; diff --git a/frontend/src/hooks/api/useNotices.js b/frontend/src/hooks/api/useNotices.js index dd4b671..7d7010f 100644 --- a/frontend/src/hooks/api/useNotices.js +++ b/frontend/src/hooks/api/useNotices.js @@ -1,10 +1,17 @@ -import { useSuspenseQuery } from '@tanstack/react-query'; +import { useSuspenseInfiniteQuery } from '@tanstack/react-query'; import instance from '../../utils/axios/instance'; -import { API_URL } from '../../constants'; +import { API_URL, PAGE_SIZE } from '../../constants'; -export function useNotices(lectureId, page = 0) { - return useSuspenseQuery({ - queryKey: ['noticelist', lectureId, page], - queryFn: () => instance.get(`${API_URL}/board?lectureId=${lectureId}&category=announcement&pageNo=${page}`), +export function useNotices(lectureId) { + return useSuspenseInfiniteQuery({ + queryKey: ['noticelist', lectureId], + queryFn: ({ pageParam = 0 }) => + instance.get(`${API_URL}/board?lectureId=${lectureId}&category=announcement&pageNo=${pageParam}`), + getNextPageParam: (lastPage, allPages) => { + if (lastPage.data.length < PAGE_SIZE) { + return undefined; + } + return allPages.length + 1; + }, }); } diff --git a/frontend/src/pages/LearningLectureDetailPage/LearningLectureDetailPage.jsx b/frontend/src/pages/LearningLectureDetailPage/LearningLectureDetailPage.jsx index 454c036..f9177df 100644 --- a/frontend/src/pages/LearningLectureDetailPage/LearningLectureDetailPage.jsx +++ b/frontend/src/pages/LearningLectureDetailPage/LearningLectureDetailPage.jsx @@ -7,7 +7,7 @@ import { useQnas } from '../../hooks/api/useQnas'; export default function LearningLectureDetailPage() { const { lectureId } = useParams(); const { data: noticesData } = useNotices(lectureId); - const notices = noticesData?.data.slice(0, 3); + const notices = noticesData.pages[0]?.data.slice(0, 3); const { data: qnasData } = useQnas(lectureId); const questions = qnasData?.data.slice(0, 3); diff --git a/frontend/src/pages/NoticeListPage/NoticeListPage.jsx b/frontend/src/pages/NoticeListPage/NoticeListPage.jsx index 3aaad61..8d5f387 100644 --- a/frontend/src/pages/NoticeListPage/NoticeListPage.jsx +++ b/frontend/src/pages/NoticeListPage/NoticeListPage.jsx @@ -3,11 +3,12 @@ import ArticleBoard from '../../components/ArticleBoard/ArticleBoard'; import { useNotices } from '../../hooks/api/useNotices'; import { useParams } from 'react-router-dom'; import useBoundStore from '../../store'; +import IntersectionArea from '../../components/IntersectionArea/IntersectionObserver'; export default function NoticeListPage() { const { lectureId } = useParams(); - const { data } = useNotices(lectureId); - const notices = data?.data; + const { data, fetchNextPage, hasNextPage } = useNotices(lectureId); + const notices = data?.pages.flatMap((page) => page.data); const userType = useBoundStore((state) => state.userType); return ( @@ -24,6 +25,7 @@ export default function NoticeListPage() { to={`${notice.id}`} /> ))} + {hasNextPage && fetchNextPage()} />} ); }