stack-analysis
CHAPTER 7 / 90
읽기 약 2분
FUNCTION
데이터 페칭: TanStack Query 실전
핵심 개념
useQuery·useMutation·캐싱·낙관적 업데이트 — API 목록·무한 스크롤·좋아요.
본문
기본 useQuery
// pip install @tanstack/react-query
import { useQuery } from '@tanstack/react-query';
function ProductList() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: async () => {
const res = await fetch('/api/products');
if (!res.ok) throw new Error('Failed');
return res.json();
},
staleTime: 1000 * 60 * 5, // 5분간 fresh
gcTime: 1000 * 60 * 30, // 30분 후 GC
});
if (isLoading) return <div>로딩 중...</div>;
if (error) return <div>오류: {(error as Error).message}</div>;
return (
<ul>
{data.map((p: Product) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}useMutation + 낙관적 업데이트
import { useMutation, useQueryClient } from '@tanstack/react-query';
function LikeButton({ postId }: { postId: string }) {
const qc = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: async ({ postId, liked }: { postId: string; liked: boolean }) => {
const method = liked ? 'POST' : 'DELETE';
const res = await fetch(`/api/posts/${postId}/like`, { method });
if (!res.ok) throw new Error('Failed');
return res.json();
},
// 낙관적 업데이트 — 즉시 UI 변경
onMutate: async ({ postId, liked }) => {
await qc.cancelQueries({ queryKey: ['post', postId] });
const prev = qc.getQueryData(['post', postId]);
qc.setQueryData(['post', postId], (old: any) => ({
...old,
liked,
likesCount: old.likesCount + (liked ? 1 : -1),
}));
return { prev };
},
// 실패 시 롤백
onError: (err, vars, context) => {
qc.setQueryData(['post', vars.postId], context?.prev);
},
// 성공·실패 모두 후 — 서버 데이터로 동기화
onSettled: (data, err, vars) => {
qc.invalidateQueries({ queryKey: ['post', vars.postId] });
},
});
const post = qc.getQueryData<Post>(['post', postId])!;
return (
<button
onClick={() => mutate({ postId, liked: !post.liked })}
disabled={isPending}
>
{post.liked ? '❤️' : '🤍'} {post.likesCount}
</button>
);
}무한 스크롤 — useInfiniteQuery
import { useInfiniteQuery } from '@tanstack/react-query';
function InfinitePostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 0 }) => {
const res = await fetch(`/api/posts?cursor=${pageParam}`);
return res.json(); // { items: Post[], nextCursor: number | null }
},
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
return (
<div>
{data?.pages.flatMap(page =>
page.items.map((post: Post) => <PostCard key={post.id} post={post} />)
)}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage ? '로드 중...' : hasNextPage ? '더 보기' : '끝'}
</button>
</div>
);
}
// IntersectionObserver로 자동 로드
import { useEffect, useRef } from 'react';
function useInfiniteScroll(callback: () => void) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => entry.isIntersecting && callback(),
{ threshold: 1.0 }
);
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, [callback]);
return ref;
}QueryClient 설정
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
gcTime: 1000 * 60 * 5,
retry: 1,
refetchOnWindowFocus: false, // 모바일 적합
},
},
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}다음 챕터
CH.8 "폼 관리: React Hook Form + Zod" — 검증의 표준.
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 코드의 데이터 페칭 부분을 분석해서 실전 분석 + 개선 우선순위를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
데이터 페칭 관련 인기 라이브러리/패턴 5개를 비교 분석해서 패턴 추출를 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 데이터 페칭 최적화 가능 위치를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 프론트엔드 시장의 데이터 페칭 트렌드를 솔직히 알려줘.
⭐ 이것만 기억하세요
데이터 페칭: TanStack Query 실전은 이 3가지만 확실히 잡으세요
1.TanStack Query는 "서버 상태"의 표준 — 캐싱·중복 제거·자동 재요청 자동
2.낙관적 업데이트로 UI 즉시 반영 + 실패 시 롤백 = 빠른 UX
3.다음 챕터 CH.8에서 React Hook Form + Zod — 폼의 표준
공유하기
진행도 7 / 90