TanStack Query 완벽 가이드 | React Query·데이터 페칭·캐싱·Mutation·실전 활용
이 글의 핵심
TanStack Query(React Query)로 효율적인 데이터 페칭을 구현하는 완벽 가이드. 캐싱, Mutation, Optimistic Update, Infinite Query까지 실전 예제로 정리. Start now.
이 글의 핵심
TanStack Query(React Query)로 효율적인 데이터 페칭을 구현하는 완벽 가이드입니다. 캐싱, Mutation, Optimistic Update, Infinite Query까지 실전 예제로 정리했습니다.
실무 경험 공유: useEffect + fetch를 TanStack Query로 전환하면서, 코드가 70% 감소하고 사용자 경험이 크게 향상된 경험을 공유합니다.
들어가며: “데이터 페칭이 복잡해요”
실무에서 마주치는 문제들
로딩 상태 관리가 어려워요
수동 관리는 번거롭습니다. TanStack Query는 자동으로 처리합니다. 캐싱이 없어요
매번 API를 호출합니다. TanStack Query는 스마트 캐싱을 제공합니다. 에러 처리가 복잡해요
일관성이 부족합니다. TanStack Query는 통일된 에러 처리를 제공합니다.
1. TanStack Query란?
핵심 특징
TanStack Query는 강력한 데이터 페칭 라이브러리입니다. 주요 장점
- 자동 캐싱: 스마트 캐시 관리
- Background Refetch: 자동 갱신
- Optimistic Update: 즉각적인 UI 업데이트
- Infinite Query: 무한 스크롤
- Devtools: 강력한 디버깅
2. 설치 및 설정
설치
npm install @tanstack/react-query
npm install -D @tanstack/react-query-devtools
Provider 설정
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export default function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
3. useQuery
기본 사용
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
},
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
파라미터가 있는 Query
function UserProfile({ userId }: { userId: number }) {
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
enabled: !!userId,
});
return <div>{user?.name}</div>;
}
4. useMutation
기본 사용
import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (newUser: { name: string; email: string }) => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
return response.json();
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
mutation.mutate({
name: formData.get('name') as string,
email: formData.get('email') as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Creating...' : 'Create User'}
</button>
{mutation.isError && <div>Error: {mutation.error.message}</div>}
</form>
);
}
5. Optimistic Update
const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
await queryClient.cancelQueries({ queryKey: ['user', newUser.id] });
const previousUser = queryClient.getQueryData(['user', newUser.id]);
queryClient.setQueryData(['user', newUser.id], newUser);
return { previousUser };
},
onError: (err, newUser, context) => {
queryClient.setQueryData(['user', newUser.id], context?.previousUser);
},
onSettled: (newUser) => {
queryClient.invalidateQueries({ queryKey: ['user', newUser.id] });
},
});
6. Infinite Query
import { useInfiniteQuery } from '@tanstack/react-query';
function Posts() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: async ({ pageParam = 1 }) => {
const response = await fetch(`/api/posts?page=${pageParam}`);
return response.json();
},
getNextPageParam: (lastPage, pages) => {
return lastPage.hasMore ? pages.length + 1 : undefined;
},
initialPageParam: 1,
});
return (
<div>
{data?.pages.map((page) =>
page.posts.map((post) => <div key={post.id}>{post.title}</div>)
)}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
7. Prefetching
import { useQueryClient } from '@tanstack/react-query';
function UserList() {
const queryClient = useQueryClient();
const handleMouseEnter = (userId: number) => {
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
};
return (
<ul>
{users.map((user) => (
<li key={user.id} onMouseEnter={() => handleMouseEnter(user.id)}>
<Link href={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
);
}
8. Devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
기능
- Query 상태 확인
- 캐시 내용 조회
- 수동 Refetch
- Query 무효화
정리 및 체크리스트
핵심 요약
- TanStack Query: 데이터 페칭 라이브러리
- 자동 캐싱: 스마트 캐시
- Mutation: 데이터 변경
- Optimistic Update: 즉각적인 UI
- Infinite Query: 무한 스크롤
- Devtools: 강력한 디버깅
구현 체크리스트
- TanStack Query 설치
- Provider 설정
- useQuery 구현
- useMutation 구현
- Optimistic Update 구현
- Infinite Query 구현
- Devtools 활용
같이 보면 좋은 글
- tRPC 완벽 가이드
- Zustand 완벽 가이드
- Next.js App Router 가이드
이 글에서 다루는 키워드
TanStack Query, React Query, Data Fetching, Cache, React, TypeScript, Frontend
자주 묻는 질문 (FAQ)
Q. SWR과 비교하면 어떤가요?
A. TanStack Query가 더 많은 기능을 제공합니다. SWR은 더 간단하지만 제한적입니다.
Q. Redux를 대체할 수 있나요?
A. 서버 상태는 대체 가능합니다. 클라이언트 상태는 Zustand 등을 함께 사용하세요.
Q. Next.js App Router에서 사용할 수 있나요?
A. 네, Server Components와 함께 사용할 수 있습니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, 수많은 기업에서 안정적으로 사용하고 있습니다.