Skip to content

Commit 4962ec4

Browse files
authored
Merge pull request #142 from oodd-team/feat/OD-225
[OD-225] Home에 스켈레톤 적용 및 쿼리 적용
2 parents 1f97f2a + a21576c commit 4962ec4

File tree

2 files changed

+48
-75
lines changed

2 files changed

+48
-75
lines changed

src/apis/post/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,15 @@ export const createPostApi = (data: CreatePostRequest) => newRequest.post<Create
1919

2020
// 게시글 리스트 조회
2121
// 전체 게시글 리스트
22-
export const getPostListApi = (page: number = 1, take: number = 10) =>
23-
newRequest.get<GetPostListResponse>(`/post`, { params: { page, take } });
22+
export const getPostListApi = async ({ pageParam = 1 }) => {
23+
const response = await newRequest.get<GetPostListResponse>('/post', {
24+
params: { page: pageParam, take: 10 },
25+
});
26+
return {
27+
posts: response.data.post,
28+
nextPage: response.data.post.length > 0 ? pageParam + 1 : undefined, // 다음 페이지 여부 확인
29+
};
30+
};
2431
// 유저 게시글 리스트
2532
export const getUserPostListApi = (page: number = 1, take: number = 10, userId: number) =>
2633
newRequest.get<GetUserPostListResponse>(`/post`, { params: { page, take, userId } });

src/pages/Home/OOTD/index.tsx

Lines changed: 39 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,79 @@
1-
import { useState, useEffect, useRef } from 'react';
1+
import { useRef, useEffect } from 'react';
22

3+
import { useInfiniteQuery } from '@tanstack/react-query';
34
import debounce from 'lodash/debounce';
45

56
import { getPostListApi } from '@apis/post';
67

7-
import type { PostSummary } from '@apis/post/dto';
8+
import Loading from '@components/Loading';
89

910
import Feed from './Feed/index';
1011

1112
import { OOTDContainer, FeedContainer } from './styles';
1213

1314
const OOTD: React.FC = () => {
14-
const [feeds, setFeeds] = useState<PostSummary[]>([]);
15-
16-
const isFetchingRef = useRef(false);
17-
const isReachedEndRef = useRef(false);
18-
const feedPageRef = useRef(1);
19-
20-
// IntersectionObserver 인스턴스를 참조하는 변수
21-
const observerRef = useRef<IntersectionObserver | null>(null);
22-
// observer 콜백 함수를 트리거하는 요소를 참조하는 변수
15+
// 무한 스크롤을 감지할 요소
2316
const loadMoreRef = useRef<HTMLDivElement | null>(null);
2417

25-
// 세션 스토리지에서 이전 스크롤 위치를 가져와 초기화
26-
const savedScrollPosition = sessionStorage.getItem('scrollPosition');
27-
const scrollPositionRef = useRef(Number(savedScrollPosition) || 0);
28-
29-
// 전체 게시글(피드) 조회 API
30-
const getPostList = async () => {
31-
// 모든 데이터를 불러왔거나 요청 중이라면 함수 실행 중단
32-
if (isReachedEndRef.current || isFetchingRef.current) return;
33-
34-
isFetchingRef.current = true;
35-
36-
try {
37-
const response = await getPostListApi(feedPageRef.current, 20);
18+
// Intersection Observer 인스턴스 저장 (컴포넌트 언마운트 시 해제 위함)
19+
const observerRef = useRef<IntersectionObserver | null>(null);
3820

39-
if (response.isSuccess) {
40-
if (response.data.post.length === 0) {
41-
isReachedEndRef.current = true;
42-
} else {
43-
setFeeds((prevFeeds) => [...prevFeeds, ...response.data.post]);
44-
feedPageRef.current += 1;
45-
}
46-
}
47-
} finally {
48-
isFetchingRef.current = false;
49-
console.log(feeds);
50-
}
51-
};
21+
// React Query를 사용한 무한 스크롤 데이터 로드
22+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useInfiniteQuery({
23+
queryKey: ['posts'], // 같은 key를 가진 쿼리는 캐시됨
24+
queryFn: ({ pageParam }) => getPostListApi({ pageParam }), // 페이지별 데이터 가져오는 함수
25+
initialPageParam: 1, // 첫 번째 페이지는 1부터 시작
26+
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, // 다음 페이지가 존재하면 page + 1, 없으면 undefined
27+
});
5228

29+
// 디버깅
5330
useEffect(() => {
54-
// 데이터의 끝에 다다르면 옵저버 해제 (더이상 피드가 없으면)
55-
if (isReachedEndRef.current && observerRef.current && loadMoreRef.current) {
56-
observerRef.current.unobserve(loadMoreRef.current);
31+
console.log('Query Status:', status);
32+
console.log('Fetched Data:', data);
33+
console.log('Fetching Next Page:', isFetchingNextPage);
34+
console.log('Has Next Page:', hasNextPage);
35+
}, [status, data, isFetchingNextPage, hasNextPage]);
5736

58-
return;
59-
}
37+
// Intersection Observer를 설정하여 스크롤이 마지막 요소에 닿았을 때 fetchNextPage 호출
38+
useEffect(() => {
39+
if (!loadMoreRef.current || !hasNextPage) return; // 다음 페이지가 없으면 실행 X
6040

6141
// Intersection Observer 생성
6242
observerRef.current = new IntersectionObserver(
6343
debounce((entries) => {
64-
const target = entries[0];
65-
console.log('Intersection Observer:', target.isIntersecting);
66-
if (target.isIntersecting && !isFetchingRef.current && !isReachedEndRef.current) {
67-
getPostList();
44+
// 요소가 화면에 보이면 fetchNextPage 호출 (스크롤 트리거)
45+
if (entries[0].isIntersecting) {
46+
fetchNextPage();
6847
}
69-
}, 300),
48+
}, 300), // 디바운싱 적용 (300ms 내 반복 호출 방지)
7049
{
7150
root: null,
7251
rootMargin: '100px',
7352
threshold: 0,
7453
},
7554
);
7655

77-
// 옵저버를 마지막 요소에 연결
78-
if (loadMoreRef.current) {
79-
observerRef.current.observe(loadMoreRef.current);
80-
}
81-
return () => {
82-
// 컴포넌트 언마운트 시 옵저버 해제
83-
if (observerRef.current && loadMoreRef.current) {
84-
observerRef.current.unobserve(loadMoreRef.current);
85-
}
86-
};
87-
}, []);
88-
89-
useEffect(() => {
90-
getPostList();
91-
92-
// 세션에 저장된 이전 스크롤 위치 복원
93-
window.scrollTo(0, scrollPositionRef.current);
56+
// 옵저버를 마지막 요소(loadMoreRef)에 연결
57+
observerRef.current.observe(loadMoreRef.current);
9458

9559
return () => {
96-
// 컴포넌트 언마운트 시 현재 스크롤 위치를 세션 스토리지에 저장
97-
sessionStorage.setItem('scrollPosition', String(window.scrollY));
60+
// 컴포넌트 언마운트 시 옵저버 해제
61+
observerRef.current?.disconnect();
9862
};
99-
}, []);
63+
}, [hasNextPage, fetchNextPage]);
10064

10165
return (
10266
<OOTDContainer>
10367
<FeedContainer>
104-
{feeds.map((feed) => (
105-
<div key={feed.id}>
106-
<Feed feed={feed} />
107-
</div>
108-
))}
109-
{/* Intersection Observer가 감지할 마지막 요소 */}
68+
{data?.pages.flatMap((page) =>
69+
page.posts.map((feed) => (
70+
<div key={feed.id}>
71+
<Feed feed={feed} />
72+
</div>
73+
)),
74+
)}
11075
<div ref={loadMoreRef} />
76+
{isFetchingNextPage && <Loading />}
11177
</FeedContainer>
11278
</OOTDContainer>
11379
);

0 commit comments

Comments
 (0)