이번 한입 크기로 잘라먹는 SNS 강의를 들으면서, TanStack Query를 다시 정리해보았다.
TanStack Query는 이전 명칭인 React Query의 확장 버전으로,
서버로부터 데이터 가져오기, 데이터 캐싱, 캐시 제어 등 데이터를 쉽고 효율적으로 관리할 수 있는 라이브러리이다.
강력한 캐싱 기능을 이용해 앱 최적화 하거나 적절한 타이밍에 캐시 데이터를 갱신 또는 삭제한다.
대표적인 기능으로는 아래와 같다.
TanStack Query는 캐시한 데이터를 신선(Fresh)하거나 상한(Stale) 상태로 구분해 관리합니다.
캐시된 데이터가 신선하다면 캐시된 데이터를 사용하고, 만약 데이터가 상했다면 서버에 다시 요청해 신선한(새로운) 데이터를 가져온다.
일종의 데이터 유통기한 정도로 생각하면 이해하기 쉽다.


위의 이미지처럼 TanStack Query는 5가지 상태를 가진다.
네트워크로 데이터를 가져오는 중인 상태
컴포넌트가 useQuery로 마운트 될 때, refetch() 호출 시 시작된다.
데이터를 신선한 상태로 유지하여 refetch 대상에서 벗어남
데이터를 받아오면 바로 fresh 상태가 되고, staleTime 동안 유지된다.
캐시에 데이터를 유지하지만 fresh 상태가 아닌 갱신이 필요한 상태
화면에는 여전히 이전 데이터를 보여주지만, 내부적으로 업데이트가 필요한 상태로, staleTime이 지나면 자동으로 stale 상태로 바뀐다.

stale 상태에서는 refetching 기능을 통해 데이터를 다시 불러와 fetching 상태로 돌아간다.
이 데이터를 사용하는 컴포넌트가 다시 마운트될 때 (페이지 전환 후 복귀)
사용자가 브라우저 탭을 떠났다가 다시 돌아올 때
refetchOnWindowFocus - true(기본값)
네트워크가 끊겼다가 다시 연결될 때
refetchOnReconnect - true(기본값)
일정 주기로 자동 refetching
refetchInterval - ms단위

캐시 데이터가 현재 사용되지 않는 상태
불필요한 캐시가 많을수록 메모리 낭비로 이어지므로, inactive 상태는 적을수록 좋다.
컴포넌트가 언마운트 시 전환된다.
캐시 보존 시간(gcTime)이 끝난 상태
gcTime(garbage collecting time) 만료 시 삭제되며, 기본 설정은 5분(300000ms)이다.
staleTime, gcTime은 독립적으로 동작하는 시간이므로, staleTime이 남아있어도 gcTime이 지나면 해당 데이터는 사라진다.
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
queryKey
데이터를 식별하는 고유한 키로, 캐시의 주소 역할을 한다.
queryFn
데이터를 실제로 가져오는 비동기 함수 요청이 들어간다.
isLoading
데이터를 처음 불러오는 상태(fetching 중)
isError
쿼리 실행 중 에러가 발생했는지 여부를 나타내는 불리언 값
요청 실패 시 true가 된다.
error
실제 에러 객체(예: AxiosError, FetchError 등)
error.message 등을 이용해 에러 원인을 출력하거나 로그에 남길 수 있다.
refetch
쿼리를 수동으로 다시 실행(refetch) 하는 함수
자동 refetch 조건(windowFocus, reconnect, staleTime 경과 등) 외에
사용자가 직접 데이터 새로고침 버튼을 눌렀을 때 호출할 수 있다.
enabled
export function useTodoDataById(id:number) {
return useQuery({
queryFn: () => fetchTodoById(id),
queryKey: ["todos", id],
staleTime: 5000,
gcTime: 5000,
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchInterval: false,
})
}
staleTime
데이터를 fresh 상태로 유지하는 시간(ms)
설정한 시간이 지나면 데이터는 stale 상태로 바뀌고, 다름 refetch 때 새로 요청된다.
staleTime이 길수록 api 호출은 줄지만 최신성은 낮아진다.
gcTime
캐시가 inactive 상태로 전환된 후 메모리에 남아있는 시간(ms)
기본값은 5분(300000ms)
시간이 지나면 해당 캐시는 메모리에서 완전히 삭제된다.
refetchOnMount
컴포넌트가 다시 마운트 될 때 자동으로 refetch 할지 여부
true : 항상 다시 요청
false : stale 상태일 때만 refetch 하지 않음
"always" : fresh여도 무조건 refetch
보통의 경우 false로 설정하여 불필요한 api 호출을 방지한다.
refetchOnWindowFocus
브라우저 탭을 다시 활성화할 때 자동으로 refetch 할지 여부
UX 상 깜빡임이 불필요하면 false로 끄는 것이 일반적이다.
기본값 : true
refetchOnReconnect
네트워크가 끊겼다가 복구되었을 때 자동으로 refetch 할지 여부
오프라인 모드 지원 앱이 아니라면 그대로 두는 것이 안전하다.
기본값 : true
refetchInterval
일정 주기로 데이터를 다시 요청(ms)
false로 설정할 경우 자동 refetch 기능을 종료한다.
refetchInterval: 5000 → 5초마다 자동 갱신
주로 실시간 데이터(알림 로그 등)에만 사용하는 것이 좋다.
useMutation은 서버에 데이터를 생성(Create), 수정(Update), 삭제(Delete) 하는 요청을 보낼 때 사용하는 훅이다.
useQuery가 데이터 가져오기(Read)에 초점을 둔다면, useMutation은 데이터 변경에 초점을 둔다.
요청 성공/실패에 따른 후속 처리(알림, 페이지 이동, 캐시 갱신 등)를 간단하게 관리할 수 있다.
onMutate → onError → onSuccess/onSettled의 순서로 실행되며,
이 과정에서 UI 선업데이트(Optimistic Update), 실패 시 롤백, 성공 시 최신화가 자연스럽게 처리된다.
import { createTodo } from "@/api/create-todo";
import { useMutation } from "@tanstack/react-query";
export function useCreateTodoMutation() {
return useMutation({
mutationFn: createTodo,
onMutate: () => {},
onSettled: () => {},
onSuccess: () => {
window.location.reload();
},
onError: (error) => {
window.alert(error.message);
},
})
}
mutationFn
실제로 데이터를 변경(등록, 수정, 삭제) 하는 비동기 함수
서버 요청을 담당하는 함수(예: axios, fetch 등)를 넣는다.
useQuery의 queryFn과 대응되는 개념
onMutate
요청이 서버로 전송되기 직전에 실행
보통 낙관적 업데이트를 적용하여, 서버 응답을 기다리지 않고 UI를 먼저 갱신함으로써 자연스러운 화면 전환을 제공한다.
이전 데이터를 임시로 저장해둘 때 사용한다.
onSettled
요청의 성공 여부와 상관없이 항상 실행
보통 invalidateQueries를 이용해 최신 데이터를 다시 가져오는 데 사용한다.
onSuccess
요청이 성공적으로 완료된 직후 실행
성공 메시지, 페이지 이동, 캐시 무효화, 새로고침 등을 처리할 때 사용한다.(코드에서는 새로고침으로 사용 중)
onError
요청이 실패했을 때 실행
에러 메시지를 표시하거나, onMutate에서 변경한 데이터를 복원할 때 사용한다. (코드에서는alert 창으로 에러 알림)
useMutation 성공 시 관련 데이터를 다시 가져오기 위해 사용하는 함수
invalidateQueries를 호출하면 해당 쿼리의 캐시를 stale 상태로 만들고, 즉시 refetch 하여 서버와 UI 상태를 자동으로 동기화한다.
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["todos"] });
}
클라이언트 측에서 현재의 QueryClient 인스턴스에 접근할 수 있도록 react query에서 제공하는 훅으로,
React Query가 내부적으로 관리하는 캐시를 직접 읽고 수정할 수 있는 유일한 통로이다.
간단히 말하면 React Query의 전역 상태 관리자에 접근하는 문으로 생각하면 된다.
useQueryClient 사용시기
서버 응답 없이 UI를 즉시 업데이트 할 때
setQueryData(), getQueryData(), cancelQuerise()
요청 실패 시 기존 상태로 되돌리기 위해
setQueryData()
refetch 없이 UI만 업데이트 할 때
setQueryData()
특정 쿼리만 새로고침하고 싶을 때
invalidateQueries()
로그아웃 시 사용자 관련 캐시를 삭제할 때
removeQueries()
Devtools를 통해 각 쿼리의 현재 상태(fresh, stale, fetching, inactive)와 캐시 데이터, 요청 이력, 리패치 타이밍 등을 실시간으로 확인할 수 있다.
쿼리 키(queryKey)별 캐시가 어떻게 관리되고 있는지 한눈에 볼 수 있어, staleTime, gcTime, refetch 옵션 조정 시 매우 유용하다.
개발 환경에서만 사용하는 것을 권장한다.
devtools 설치 / 적용
npm i @tanstack/react-query-devtools
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
import { BrowserRouter } from "react-router";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const queryClient = new QueryClient();
createRoot(document.getElementById("root")!).render(
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools />
<App />
</QueryClientProvider>
</BrowserRouter>,
);
오늘은 TanStack Query를 처음부터 다시 정리해보았다.
그동안은 필요할 때마다 문서를 찾아보거나 예제 코드를 참고하면서 사용했지만, 이번에 전체적으로 정리해보니 앞으로는 훨씬 더 자연스럽게 활용할 수 있을 것 같다.