useInfiniteQuery로 무한 스크롤 구현하기

srchae·2025년 6월 6일
post-thumbnail

시작하기 😀

이번에 회사에서 새롭게 개발하는 앱에서 게시글을 불러올 때의 무한 스크롤 구현이 필요했고, 이를 위해 useInfiniteQuery라는 훅을 사용하게 되었다.

useInfiniteQuery 훅은 “끝이 없는” 페이징 데이터를 다룰 때 쓰는 훅으로, 스크롤을 내릴 때 추가 페이지를 불러오거나 “더 보기(load more)” 버튼 등을 활용해 데이터를 이어서 가져올 때 주로 활용한다.

사용 방법

1. 데이터 가져오기

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이 되었을 때는 쿼리 실행을 멈춘다.

2. 데이터 렌더링

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()를 호출해 다음 페이지 데이터를 가져온다.

profile
🐥집요함과 꾸준함🪽

0개의 댓글