
이번에 회사에서 새롭게 개발하는 앱에서 게시글을 불러올 때의 무한 스크롤 구현이 필요했고, 이를 위해 useInfiniteQuery라는 훅을 사용하게 되었다.
useInfiniteQuery훅은 “끝이 없는” 페이징 데이터를 다룰 때 쓰는 훅으로, 스크롤을 내릴 때 추가 페이지를 불러오거나 “더 보기(load more)” 버튼 등을 활용해 데이터를 이어서 가져올 때 주로 활용한다.
const {
data: magazines,
refetch,
hasNextPage, // 다음 페이지가 있는지 여부
isFetchingNextPage, // 다음 페이지를 현재 불러오고 있는지 여부
fetchNextPage, // 다음 페이지를 요청하는 트리거
} = useInfiniteQuery<BoardListResponse>({
queryKey: ["magazines", locationId, categoryId],
initialPageParam: null, // 최초 호출 시 cursor 값
queryFn: ({ pageParam }) =>
api.getVillageBoard({
locationId: locationId as number,
categoryId,
cursor: pageParam as number,
}),
getNextPageParam: (lastPage) => {
if (lastPage.total === 0) return undefined;
return lastPage.boards.at(-1)?.id;
}, // 다음 요청에 넘길 cursor 결정
enabled: !!locationId, // locationId가 없으면 쿼리 미실행
});
현재 API의 경우, cursor를 파라미터로 받는다.
서버에서 5개씩 게시글을 보여주게 되어있고, 마지막 게시글의 id 값을 cursor로 담아 요청을 계속한다.
또, total이라는 필드가 0이 되었을 때는 쿼리 실행을 멈춘다.
const allBoards = useMemo(
() => magazines?.pages.flatMap((p) => p.boards) ?? [],
[magazines]
);
<FlatList
data={allBoards}
renderItem={renderItem}
keyExtractor={(item) => String(item.id)}
contentContainerStyle={styles.listContainer}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
onEndReached={() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}} // 리스트의 마지막에 스크롤 되었을 때 트리거 (다음 페이지 호출)
onEndReachedThreshold={0.5} // 끝에 얼마나 가까워졌는지의 기준값 (리스트의 50% 쯤)
ListFooterComponent={
isFetchingNextPage ? (
<View style={{ paddingVertical: 20 }}>
{isFetchingNextPage && (
<ActivityIndicator size="large" color="#888" animating />
)}
</View>
) : null
} // 다음 페이지를 불러오고 있는지에 따른 로딩 스피너 표시
/>
{allBoards.length === 0 && (
<View style={{ flex: 1, alignItems: "center" }}>
<CustomText style={{ fontSize: 18, color: "#777" }}>
등록된 게시글이 없습니다
</CustomText>
</View>
)}
FlatList로 게시글을 렌더링하다가 스크롤이 리스트 하단(End)에 가까워지면, fetchNextPage()를 호출해 다음 페이지 데이터를 가져온다.