useQuery는 비동기 데이터를 관리하는 훅으로 API 요청을 단순화하고 캐싱, 로딩/에러 상태 관리까지 자동으로 처리해준다.
기존에는 useEffect와 useState를 조합하여 데이터를 불러와야 했지만 useQuery를 사용하면 훨씬 간결하게 데이터를 관리할 수 있다. 또한 캐싱, 자동 리페치, 서버 상태 관리 기능을 제공하여 서버 데이터 관리의 복잡성을 크게 줄여준다.
이 기능은 TanStack Query 라이브러리에서 제공된다.
useQuery를 사용하기 위해 먼저 TanStack Query를 설치한다.
[설치] npm install @tanstack/react-query
그 다음 QueryClientProvider로 앱을 감싸서 React Query를 사용할 수 있도록 설정한다.
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ReactDOM from "react-dom/client";
import App from "./App";
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
다음은 블로그 카테고리 목록을 호출하기 위한 useQuery 예시이다.
import { useQuery } from "@tanstack/react-query";
import { categoryService } from "@/services/categoryService";
import { Category } from "@/types/category";
export const useCategories = () => {
return useQuery<Category[]>({
queryKey: ["categories"],
queryFn: () => categoryService.getCategories(),
staleTime: 1000 * 60 * 60 * 24, // 24시간
gcTime: 1000 * 60 * 60 * 24 * 7, // 7일
refetchOnWindowFocus: false,
});
};
이렇게 만든 커스텀 훅을 컴포넌트에서 호출하여 데이터 상태에 따라 렌더링을 제어할 수 있다.
import React from "react";
import { useCategories } from "@/hooks/useCategories";
export default function CategoryList() {
const { data, isLoading, error } = useCategories();
if (isLoading) return <p>카테고리를 불러오는 중입니다.</p>;
if (error) return <p>데이터를 불러오는 데 실패했습니다.</p>;
return (
<ul>
{data?.map((category) => (
<li key={category.id}>{category.name}</li>
))}
</ul>
);
}
useQuery에는 몇가지 주요 옵션들이 존재한다.
캐싱과 리페치를 관리하는 고유 식별자이다. TanStack Query v4부터는 반드시 배열 형태로 작성해야 한다.
["categories"]
["user", userId]
["posts", { page: 1 }]
queryKey를 잘 설계하면 파라미터 기반 캐싱이 가능하다.
useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
});
userId가 변경되면 자동으로 새로운 요청이 발생한다.
데이터를 가져오는 비동기 함수이다. 보통 API 요청 함수가 들어간다.
queryFn: () => categoryService.getCategories()
쿼리 실행 여부를 제어하는 옵션이다. 특정 조건이 만족할 때만 요청을 보내고 싶을 때 사용한다.
useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
enabled: !!userId,
});
로그인 이후 데이터를 요청해야 하거나 특정 값이 선택된 이후 API를 호출해야하는 상황 등에서 유용하다.
데이터가 신선한 상태(fresh)로 유지되는 시간이다. 이 시간 동안은 서버에 다시 요청하지 않고 캐시된 데이터를 사용한다.
staleTime: 1000 * 60 * 60 * 24
데이터가 비활성 상태(Inactive)가 된 이후 캐시가 메모리에 유지되는 시간이다. 쿼리를 사용하는 컴포넌트가 언마운트되면 타이머가 시작되며 이 시간이 지나면 캐시 데이터가 삭제된다. gcTime은 캐시가 언제 삭제될지를 결정하는 옵션이며 staleTime과는 역할이 다르다. staleTime은 언제 다시 서버 요청을 할지와 관련된 옵션이고 gcTime은 캐시 데이터를 언제 메모리에서 삭제할지와 관련된 옵션이다.
요청 실패 시 자동으로 재시도하는 횟수이다. 기본값은 3회이다.
retry: 3
브라우저 창이 다시 포커스 되었을 때 자동으로 데이터를 리페치할지 여부를 설정한다.
refetchOnWindowFocus: false
useQuery는 다양한 상태 값을 제공하여 비동기 데이터를 쉽게 관리할 수 있다.
const {
data,
error,
isLoading,
isError,
isFetching,
refetch,
status
} = useQuery(...)
주요 상태 값은 다음과 같다.
이를 활용하여 기존 데이터를 유지하면서 백그라운드 요청 상태를 표시할 수 있다.
{isFetching && <Spinner />}
select 옵션을 사용하면 API 응답 데이터를 바로 가공할 수 있다.
useQuery({
queryKey: ["categories"],
queryFn: getCategories,
select: (data) => data.map((category) => category.name),
});
이렇게 하면 컴포넌트에서는 가공된 데이터만 사용할 수 있다.
데이터가 변경되었을 때 캐시를 무효화하고 다시 데이터를 가져오도록 할 수 있다.
import { useQueryClient } from "@tanstack/react-query";
const queryClient = useQueryClient();
queryClient.invalidateQueries({
queryKey: ["categories"]
});
보통 데이터 생성, 수정, 삭제 이후에 사용되며 게시글 작성, 댓글 삭제, 카테고리 수정 이후 목록 데이터를 다시 불러올 때 사용한다.
개발 중에는 React Query Devtools를 사용하면 캐시 상태를 쉽게 확인할 수 있다.
[설치] npm install @tanstack/react-query-devtools
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
<ReactQueryDevtools initialIsOpen={false} />
Devtools를 사용하면 쿼리 캐시 상태, 로딩 상태, 데이터 변경등을 UI에서 확인할 수 있다.
