useNexusQuery
useNexusQuery는 definition으로 정의된 데이터를 클라이언트에서 가져오고, 캐시하고, 재검증하기 위한 훅입니다. 클라이언트 컴포넌트에서 데이터를 읽기 위해 설계되었습니다.
Import
import { useNexusQuery } from 'next-nexus/client';시그니처 (Signature)
useNexusQuery<TData, TSelectedData = TData>(
definition: NexusDefinition<TData>,
options?: { ... }
): {
data: TSelectedData | undefined
headers: Headers | undefined
error: Error | null
isPending: boolean
isPendingBackground: boolean
isSuccess: boolean
isError: boolean
revalidate: () => Promise<void>
}definition:GET메서드를 사용하는definition객체.GET이 아닌definition이 전달되면 오류가 발생합니다.options: 훅의 동작을 설정하는 객체. 아래 Options를 참고하세요.
핵심 개념 (Core Concepts)
하이드레이션 인식 페칭 (Hydration-Aware Fetching)
useNexusQuery는 서버에서 페치되어 하이드레이션으로 클라이언트에 전달된 데이터를 인식합니다. 마운트 시점에 클라이언트 캐시에 데이터가 있으면, 새로운 요청 없이 즉시 렌더링하여 UI 깜빡임을 제거합니다.
Foreground/Background 재검증
revalidate() 함수가 호출되거나 마운트, 창 포커스 등 자동 재검증 이벤트가 발생할 때 훅이 자동으로 재검증 모드를 결정합니다.
- Foreground: 캐시된 데이터가 없거나,
keepStaleData가false이고 데이터가 만료(stale)된 경우. 이 때isPending이true가 됩니다. - Background: 만료된 데이터가 있고
keepStaleData가true(기본값)인 경우. 이 때isPending은false인 채로isPendingBackground가true가 됩니다. - 실행 안 함 (No-op): 신선한(fresh) 데이터가 있으면 요청을 수행하지 않습니다.
상태 플래그와 UX (State Flags and UX)
isPending: Foreground 재검증 중에true가 됩니다. 아직 데이터가 없을 때(isPending && !data) 스피너와 같은 로딩 상태를 표시하는 데 사용하세요.isPendingBackground: Background 재검증 중에true가 됩니다. 만료된 데이터를 화면에 계속 보여주면서, ‘새로고침’ 버튼을 비활성화하는 등 미묘한 로딩 표시를 위해 사용하세요.
Options
route?:string- 요청을 Next.js 라우트 핸들러로 프록시합니다. 이 옵션이 제공되면 런타임은definition의baseURL을 지우고route를 유효 엔드포인트로 사용합니다.enabled?:boolean(기본값:true) -false로 설정하면 훅이 데이터를 가져오거나 캐시 업데이트를 구독하지 않습니다.select?:(data: TData) => TSelectedData- 데이터를 선택하거나 변환하는 함수. 선택된 값이 변경될 때만 컴포넌트가 리렌더링됩니다.revalidateOnWindowFocus?:boolean(기본값:true) - 창이 다시 포커스될 때 자동으로 재검증합니다.revalidateOnMount?:boolean(기본값:true) - 데이터가 만료 상태일 때 마운트 시 자동으로 재검증합니다.keepStaleData?:boolean(기본값:true) -true이면 Background 재검증이 발생하는 동안 만료된 데이터가 화면에 유지됩니다.false이면 Foreground 재검증 중에data가undefined가 됩니다.
예제 (Examples)
기본 사용법
'use client';
import { useNexusQuery } from 'next-nexus/client';
import { productDefinition } from '@/api/productDefinition';
export function ProductListClient() {
const { data, isPending } = useNexusQuery(productDefinition.list);
if (isPending && !data) return <div>로딩 중...</div>;
const products = data ?? [];
return (
<ul>
{products.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}select 옵션 사용하기
'use client';
import { useNexusQuery } from 'next-nexus/client';
import { statsDefinition } from '@/api/statsDefinition';
export function TotalCount() {
const { data: count } = useNexusQuery(statsDefinition.summary, {
select: s => s.total,
});
return <div>총계: {count ?? 0}</div>;
}route 오버라이드 사용하기
'use client';
import { useNexusQuery } from 'next-nexus/client';
import { productDefinition } from '@/api/productDefinition';
export function ProductListViaRoute() {
const { data } = useNexusQuery(productDefinition.list, { route: '/api/products' });
return <div>{data?.length ?? 0}개 항목</div>;
}캐시된 헤더 읽기
'use client';
import { useNexusQuery } from 'next-nexus/client';
import { listWithTotalDefinition } from '@/api/productDefinition'; // 이 definition이 있다고 가정
export function ProductListWithTotal() {
const { data, headers } = useNexusQuery(listWithTotalDefinition);
const total = headers?.get('x-total-count');
return (
<div>
항목: {data?.length ?? 0} / 전체: {total ?? 'n/a'}
</div>
);
}함께 보기