
공식문서 간판에서 TanStack Query는 강력한 상태 관리를 제공한다고 설명한다
서버 상태 데이터를 관리하기 위해 사용한다
useQuery()는 백엔드로부터 데이터를 받아오는 훅이다
// 사용 예시
import { useQuery } from '@tanstack/react-query';
import { getPosts } from './api';
function HomePage() {
const result = useQuery({ queryKey: ['posts'], queryFn: getPosts });
console.log(result);
return <div>홈페이지</div>;
}
export default HomePage;
TanStack Query는 Query Status와 Fetch Status, 2가지 status를 가진다
Query Status는 실제 받아온 data 값의 유무를 나타내는 상태 값이다
1) pending: 아직 데이터를 받아오지 못한 상태
2) success: 데이터를 성공적으로 받아온 상태
3) error: 데이터를 받아오는 중에 에러가 발생한 상태
Fetch Status는 queryFn() 함수가 현재 실행되고 있는지의 유무를 나타내는 상태 값이다
1) fetching: 쿼리 함수가 실행 되는 중
2) paused: 쿼리 함수가 시작은 했는데 실제 실행되고 있지 않는 중 (주로 네트워크가 오프라인이 된 경우)
3) idle: 쿼리 함수가 어떤 작업도 하고 있지 않은 상황 (fetching이나 paused가 아닌 상태)

enabled 값이 true일 때에만 쿼리가 실행되도록 함
// 예시
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: getUserByEmail,
});
const userId = user?.id
const {
data: projects,
} = useQuery({
queryKey: ['projects', userId],
queryFn: getProjectsByUser,
enabled: !!userId, // userId 값이 true일 때에만 동작한다
});
페이지가 새롭게 바뀔 때 매번 pending 상태가 되지 않고, 이전의 데이터를 유지해서 보여주다가 새로운 데이터가 fetch 되면 자연스럽게 새로운 데이터로 바꿔서 보여준다
// 예시
import {
// ...
keepPreviousData,
} from '@tanstack/react-query';
const {
data: postsData,
isPending,
isError,
} = useQuery({
queryKey: ['posts', page],
queryFn: () => getPosts(page, PAGE_LIMIT),
placeholderData: keepPreviousData,
// or placeholderData: (prevData) => prevData,
});
// A list of todos
useQuery({ queryKey: ['todos'], ... })
// Something else, whatever!
useQuery({ queryKey: ['something', 'special'], ... })

isPending, isError 값을 이용해 처리할 수 있다
// 예시
function HomePage() {
const {
data: postsData,
isPending,
isError,
} = useQuery({
queryKey: ['posts'],
queryFn: getPosts,
retry: 0, // retry는 기본으로 3번의 재시도를 하고, 0으로 설정시 에러 화면을 더 빨리 볼 수 있다
});
if (isPending) return '로딩 중입니다...';
if (isError) return '에러가 발생했습니다.';
const posts = postsData?.results ?? [];
return (
<div>
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.user.name}: {post.content}
</li>
))}
</ul>
</div>
);
}
useMutation()은 DB에 새로운 값을 추가하거나, 수정, 삭제를 진행할시 사용한다
// 예시
const uploadPostMutation = useMutation({
mutationFn: (newPost) => uploadPost(newPost),
});
const handleSubmit = (e) => {
e.preventDefault();
const newPost = { username: 'codeit', content };
uploadPostMutation.mutate(newPost); // 직접 mutate 함수를 실행해 줘야 한다
setContent('');
};
useMutation() 훅을 이용해 데이터를 추가한 이후, invalidateQueries() 함수를 사용하여 자동으로 refetch를 하도록 한다
const queryClient = useQueryClient();
// ...
const uploadPostMutation = useMutation({
mutationFn: (newPost) => uploadPost(newPost),
// mutation이 성공한 시점에 콜백으로 등록한다
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
useQuery()는 data가 한 페이지의 정보만 담고 있다면, useInfiniteQuery()는 data.pages에 배열 형태로 모든 페이지의 정보를 담고 있다
// 예시
initialPageParam: 0,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPage.hasMore ? lastPageParam + 1 : undefined,
// pageParam은 쿼리 함수의 파라미터로 전달된다
queryFn: ({ pageParam }) => getPosts(pageParam, PAGE_LIMIT)
// 예시
const {
data: postsData,
isPending,
isError,
fetchNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam }) => getPosts(pageParam, PAGE_LIMIT),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPage.hasMore ? lastPageParam + 1 : undefined,
});
// ...
return (
...
<div>
// 버튼 클릭시, 다음 페이지를 불러오도록 onClick 함수로 등록해준다
<button onClick={fetchNextPage}>더 불러오기</button>
</div>
...
);
// 예시
const {
data: postsData,
isPending,
isError,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam }) => getPosts(pageParam, PAGE_LIMIT),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
lastPage.hasMore ? lastPageParam + 1 : undefined,
});
// ...
return (
...
<button
onClick={fetchNextPage}
// 이렇게!
disabled={!hasNextPage || isFetchingNextPage}
>
더 불러오기
</button>
...
);