노트

TanStack Query

TanStack Query

프런트엔드#react · 연결된 개념 20개

쉽게 말하면

TanStack Query는 서버에서 가져온 데이터를 냉장고에 넣어 두고, 오래됐다 싶으면 알아서 새로 장을 봐 오는 관리인이에요. 로딩·에러·캐시를 컴포넌트마다 손으로 챙기지 않아도 돼요.

비유가 깨지는 곳 냉장고 속 음식은 내 것이지만 서버 상태는 남이 언제든 바꿀 수 있는 원격 데이터의 스냅샷이에요. 그래서 클라이언트 상태와 같은 전역 스토어에 넣지 않고 따로 관리하는 게 핵심이에요.

TanStack Query(구 React Query)는 서버에서 가져온 데이터를 캐시하고, 오래된 데이터를 다시 가져오고, 로딩·에러 상태를 관리해 주는 비동기 상태 관리자다. 데이터 요청 함수 자체가 아니라 그 결과의 생명주기를 맡는다.

서버 상태(Server State)와 클라이언트 상태(Client State)

  • 클라이언트 상태(모달 열림, 입력값)는 앱이 소유하고, 동기적이며, 항상 최신이다
  • 서버 상태는 원격에 저장돼 있고, 비동기로 가져오며, 다른 사람이 바꿀 수 있어 언제든 낡을 수 있다. 클라이언트가 보는 것은 스냅샷일 뿐이다

이 둘을 같은 전역 스토어에 넣던 관행을 바꾼 것이 이 라이브러리의 핵심이다(Redux 이후의 상태 관리 선택).

const { data, isLoading, isFetching, error } = useQuery({
  queryKey: ['todos', { status }],       // 키가 바뀌면 다시 요청(의존성 배열 역할)
  queryFn: () => fetchTodos(status),
})
  • 오래된(stale) 쿼리는 창 포커스, 컴포넌트 마운트, 네트워크 재연결, 키 변경 때 백그라운드에서 다시 요청된다. 기준 시간은 staleTime과 gcTime
  • isLoading은 캐시가 비어 있는 첫 요청 중, isFetching은 캐시가 있어도 백그라운드 요청 중이면 true다. 보여 줄 데이터가 없을 때 스피너는 isLoading으로 판단한다
  • data는 처음엔 undefined다. 기본값을 주거나 useSuspenseQuery를 쓴다
  • select로 캐시에서 필요한 부분만 꺼내 구독할 수 있다

바꾸기와 미리 채우기

  • useMutation: 서버 데이터를 바꾸는 요청. mutate(인자)로 호출하고 상태는 isPending으로 본다. 기본으로 재시도하지 않는다. 성공 후 invalidateQueries로 관련 쿼리를 다시 가져오거나, 낙관적으로(Optimistic Update) 캐시를 먼저 고친다
  • 캐시에 넣는 방법: prefetchQuery(서버에서 미리 가져와 캐시에 넣음), setQueryData(클라이언트 값을 캐시에 넣음), initialData(캐시에 넣음), placeholderData(캐시에 넣지 않고 보여 주기만 함)
  • 무한 스크롤(Infinite Scroll)은 useInfiniteQuery(getNextPageParam, hasNextPage, fetchNextPage). 목록이 길면 리스트 가상화과 함께 쓴다
  • 에러는 반환값의 error, Error Boundary, QueryCache의 전역 onError로 처리한다
  • 테스트에서는 테스트마다 새 QueryClient를 만들고 retry: false를 준다

v4에서 v5로

2026-10 기준 React용(@tanstack/react-query)과 코어의 최신 메이저는 v5(5.104)다. v5에서 바뀐 큰 것들은 다음과 같다.

  • 객체 시그니처 하나만: useQuery(['todos'], fn, opts) 같은 위치 인자 형태가 사라지고 useQuery({ queryKey, queryFn, ...opts })만 남았다. queryClient의 메서드(invalidateQueries({ queryKey }) 등)도 같다
  • 쿼리 콜백 제거: useQuery의 onSuccess·onError·onSettled가 없어졌다. 데이터에 맞춰 다른 state를 맞추는 용도라면 렌더 중 파생하고, 에러 알림은 QueryCache의 전역 콜백으로 옮긴다. mutation의 콜백은 그대로 있다
  • isLoading → isPending: 상태 이름 loading이 pending(아직 데이터 없음)이 됐다. 새 isLoading은 isPending && isFetching, 즉 예전 isInitialLoading의 뜻이다
  • cacheTime → gcTime: 이름만 바뀌었다(staleTime과 gcTime)
  • keepPreviousData → placeholderData: keepPreviousData(isPlaceholderData), useErrorBoundary → throwOnError, Hydrate → HydrationBoundary, useInfiniteQuery는 initialPageParam 필수
  • React 18 이상이 필요하다(useSyncExternalStore 사용)

mutation 콜백은 실행 순서대로 값을 받는다. 5.104 기준 onSuccess(data, variables, onMutateResult, context)다. variables는 mutate(인자)에 넘긴 값 그대로라 "방금 저장한 폼의 날짜로 목록 이동"처럼 응답에 없는 입력값을 쓸 때 편하다. 세 번째는 onMutate가 반환한 값(낙관적 업데이트 롤백용, 예전 이름 context)이고, 네 번째 context에는 client·meta·mutationKey가 들어 있다.

서버 렌더에서 useQuery가 무엇을 실행하는지, 서버 gcTime이 메모리에 주는 영향은 서버 렌더에서 훅이 하는 일.

키 설계는 쿼리 키 설계와 키 팩토리. 서버 쪽 캐시와 층이 어떻게 다른지는 LRU 캐시와 캐시 계층 비교, 브라우저 HTTP(HyperText Transfer Protocol) 캐시와의 차이는 브라우저 캐시.

출처: TkDodo - Practical React Query · TanStack Query - Migrating to v5

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • React 상태 갱신

    React는 상태를 직접 고치지 않고 새 값을 setState로 넘겨야 다시 그린다. 이전 값과 Object.is로 비교하므로, 같은 객체를 고친 뒤 넘기면 바뀐 줄 모른다.

  • 최종 일관성

    최종 일관성(eventual consistency)은 "지금 당장은 저장소마다 값이 다를 수 있지만, 새 변경이 멈추면 결국 같아진다"는 보장이다. 원본 DB와 검색 색인·캐시·다른 서비스처럼 물리적으로 분리된 저장소를 한 트랜잭션으로 묶을 수 없을 때 받아들이는 일관성 모델(consistency model)이다.

  • 서버 함수와 서버 액션

    서버 함수(Server Function)는 'use server'로 표시해 서버에서만 실행되지만 클라이언트에서 네트워크 요청으로 호출할 수 있는 async 함수다. 그중 폼 제출이나 데이터 변경처럼 action 맥락(form action, transition)에서 쓰이는 것을 서버 액션(Server Action)이라 부른다. 서버 액션은 서버 함수의 부분집합이다.

보기 옵션