useNexusInfiniteQuery
useNexusInfiniteQuery는 ‘무한 스크롤’ 또는 ‘더 보기’ UI를 구축하기 위한 클라이언트 사이드 훅입니다. 페이지 파라미터, 데이터 페칭, 상태 집계를 관리합니다.
Import
import { useNexusInfiniteQuery } from 'next-nexus/client';핵심 개념 (Core Concepts)
동작 방식
이 훅은 페이지 파라미터(예: 커서 또는 페이지 번호)를 인자로 받는 definition 팩토리 함수를 사용합니다. 이 함수를 순차적으로 호출하여 각 페이지를 가져옵니다. 사용자는 마지막으로 성공한 페이지의 데이터로부터 다음 페이지의 파라미터를 가져오는 방법을 알려주는 getNextPageParam 함수를 반드시 제공해야 합니다.
Foreground/Background 재검증
각 페이지의 재검증 로직은 useNexusQuery와 동일합니다. 새 페이지를 가져오거나(revalidateNext) 기존 페이지를 다시 가져올 때(revalidatePage):
- Foreground: 페이지에 대한 캐시된 데이터가 없거나,
keepStaleData가false이고 데이터가 만료(stale)된 경우. 이 때isPending상태가 됩니다. - Background: 만료된 데이터가 있고
keepStaleData가true인 경우. 이 때isPendingBackground상태가 됩니다.
상태 플래그와 UX
isPending: Foreground 페칭 중에true가 됩니다. 초기 로딩 시 스켈레톤 UI나 ‘더 보기’ 버튼의 스피너와 같은 주요 로딩 상태를 표시하는 데 사용하세요.isPendingBackground: Background 페칭 중에true가 됩니다.hasNextPage: 마지막 페이지에 대해getNextPageParam함수가null또는undefined가 아닌 값을 반환했을 때true가 됩니다.
시그니처 (Signature)
파라미터 (Parameters)
getDefinition:(param) => NexusDefinition<TPage>— 현재 페이지 파라미터를 받아GETdefinition을 반환하는 팩토리 함수.options: 훅의 동작을 설정하는 객체. 아래 Options를 참고하세요.
반환 값 (Return Value)
다음을 포함하는 객체:
data:{ pages: TPage[], pageParams: unknown[] } | undefinedheaders:Headers | undefined(가장 최근 요청의 헤더)error:Error | nullisPending,isPendingBackground,isSuccess,isError:booleanhasNextPage:booleanrevalidateNext:() => Promise<void>(다음 페이지를 가져옴)prefetchRef?:React.Ref<HTMLDivElement>(선패치를 활성화하기 위한 sentinel 엘리먼트의 ref)
Options
initialPageParam: 가장 첫 페이지의 파라미터 (예: 페이지 번호는0, 커서 기반은null).getNextPageParam:(lastPage: TPage, allPages: TPage[]) => param | null | undefined— 다음 페이지의 파라미터를 반환하는 함수.null또는undefined를 반환하면 마지막 페이지임을 나타냅니다.keepPages?:number- 설정된 경우, 메모리 사용량을 제한하기 위해 가장 최근 N개의 페이지만 메모리에 유지합니다.revalidateOnMount?:boolean(기본값:true)revalidateOnWindowFocus?:boolean(기본값:false)keepStaleData?:boolean(기본값:true)prefetchNextOnNearViewport?:{ rootMargin?: string, threshold?: number }- sentinel 엘리먼트가 뷰포트 근처에 있을 때 다음 페이지를 선패치(prefetch)합니다.
예제 (Example)
1. Definition 팩토리
먼저, 커서와 같은 페이지 파라미터를 받는 definition을 만듭니다.
// src/api/productDefinition.ts (일부)
export interface InfiniteProduct {
products: { id: string; name: string }[];
nextCursor?: string | null;
}
export const productDefinition = {
infiniteList: (cursor: string | null) =>
createApiDefinition<InfiniteProduct>({
method: "GET",
endpoint: cursor ? `/products?cursor=${cursor}` : "/products",
client: {
tags: ["products", `cursor:${cursor ?? "first"}`],
revalidate: 300,
},
}),
};2. 컴포넌트 사용법
그 다음, 컴포넌트에서 훅을 사용합니다.
"use client";
import { useNexusInfiniteQuery } from "next-nexus/client";
import { productDefinition } from "@/api/productDefinition";
export default function InfiniteProductList() {
const { data, hasNextPage, revalidateNext, prefetchRef, isPending } =
useNexusInfiniteQuery(productDefinition.infiniteList, {
initialPageParam: null,
getNextPageParam: (lastPage) => lastPage?.nextCursor ?? null,
prefetchNextOnNearViewport: { rootMargin: "200px" },
});
const allProducts = data?.pages.flatMap((page) => page.products) ?? [];
return (
<div>
<ul>
{allProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
<button
onClick={() => revalidateNext()}
disabled={!hasNextPage || isPending}
>
{isPending ? "로딩 중…" : "더 보기"}
</button>
{/* 옵션: 선패치를 위한 sentinel 엘리먼트 */}
<div ref={prefetchRef} style={{ height: 1 }} />
</div>
);
}함께 보기