// HomePage.tsx
//SECTION - 스크롤 위치 기억
// 스크롤 영역에 대한 ref
const scrollAreaRef = useRef<HTMLDivElement>(null)
// 컴포넌트 마운트 시 항상 최신 데이터 조회 (staleTime이 지난 경우)
useEffect(() => {
// 페이지 진입 시 항상 최신 데이터 확인
console.log('홈 페이지 진입: 최신 데이터 확인')
refetch({ cancelRefetch: false })
}, [refetch])
// 스크롤 이벤트 리스너 등록
useEffect(() => {
const scrollElement = scrollAreaRef.current
if (!scrollElement) return
// 이벤트 리스너 등록 및 클린업 함수 받기
const cleanup = addScrollListener(scrollElement, SCROLL_KEY)
return cleanup
}, [])
// 스크롤 위치 복원
useEffect(() => {
// 새로고침이 아닌 경우에만 스크롤 위치 복원
const shouldRestoreScroll = !location.state?.refreshFeed
if (
scrollAreaRef.current &&
!isLoading &&
data?.pages &&
shouldRestoreScroll
) {
const position = getScrollPosition(SCROLL_KEY)
if (position > 0) {
setTimeout(() => {
if (scrollAreaRef.current) {
scrollAreaRef.current.scrollTop = position
}
}, 200)
}
}
}, [data, isLoading, location.state])
스크롤 영역에 아래와 같이 작성
// 다음 페이지 로드 함수
const loadMoreFeeds = useCallback(() => {
if (!isFetchingNextPage && hasNextPage) {
fetchNextPage()
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage])
<ScrollArea ref={scrollAreaRef}>
{isError ? (
<ErrorContainer>
<div>피드를 불러오는 중 오류가 발생했습니다.</div>
<div className="mt-1 text-sm">{error?.message}</div>
<RetryButton onClick={() => refetch()}>다시 시도하기</RetryButton>
</ErrorContainer>
) : (
<InfiniteScroll
onLoadMore={loadMoreFeeds}
hasNextPage={!!hasNextPage}
isLoading={isFetchingNextPage}
loadingComponent={
<LoadingContainer>
{isFetchingNextPage ? '더 많은 피드를 불러오는 중...' : ''}
</LoadingContainer>
}
>
<HomeFeedList
feeds={feeds}
isLoading={isLoading && feeds.length === 0}
/>
</InfiniteScroll>
)}
</ScrollArea>
api 호출 이후 hook 함수로 페이지네이션 관리 → 무한 스크롤
// useFeedQuery.ts
export const useFeedQuery = () => {
return useInfiniteQuery<
FeedResponse,
Error,
{ pages: FeedResponse[]; pageParams: number[] }
>({
queryKey: ['feeds', 'recommended'],
queryFn: async ({ pageParam }) => {
console.log('피드 요청 - 페이지:', pageParam)
try {
const response = await getRecommendedFeeds(Number(pageParam), 5)
return response
} catch (error) {
console.error('피드 요청 오류:', error)
throw error
}
},
initialPageParam: 0,
getNextPageParam: (lastPage) => {
// 더 가져올 데이터가 있고, 현재 페이지가 전체 페이지보다 작은 경우 다음 페이지 반환
if (!lastPage.last && lastPage.content.length > 0) {
return lastPage.currentPage + 1
}
// 마지막 페이지이거나 데이터가 없는 경우 undefined 반환 (더 이상 페이지 없음)
return undefined
},
staleTime: 1000 * 60 * 5, // 5분
refetchOnWindowFocus: false, // 창이 포커스를 얻을 때 다시 가져오지 않음
retry: 2, // 실패 시 최대 2번 재시도
// React Query 캐시가 유지되는 시간 설정 (10분)
gcTime: 1000 * 60 * 10,
})
}
HomePage 에서 조회한 피드 아래와 같이 사용
// 피드 데이터가 변경되면 Zustand 스토어에 저장
useEffect(() => {
if (data?.pages) {
const allFeeds = data.pages.flatMap((page) => page.content)
setFeeds(allFeeds)
}
}, [data, setFeeds])
// 모든 페이지의 피드를 하나의 배열로 합치기
const feeds: FeedData[] = data
? data.pages.flatMap((page) =>
page.content.map((feed) => transformFeedData(feed)),
)
: []