Tanstack Query 사용법 정리

천천히조금씩·2025년 10월 14일

React ...에 대하여

목록 보기
5/6

🎯 들어가며

오늘은 Tanstack Query (리액트 쿼리) 사용법을 정리할 것이다. 이전에 정리해둔 내용이 있지만 직접 실전에서 사용해보고 해당 내용이 너무 부실하다는 생각이 들어서 다시 정리를 하며 공부해봐야 될 듯 하다.

리액트 쿼리란 리액트 어플리케이션에서 서버 상태를 효율적으로 관리하는 라이브러리!

✍ 왜 쓸까? (장점은?)

  1. 간편한 데이터 fetching
    • 훅을 이용하여 데이터를 쉽게 가져올 수 있다.
  2. 자동 캐싱
    • 한번 가져온 데이터는 캐시에 저장되고, 동일한 요청이 반복되면 데이터를 재활용해서 네트워크 요청을 줄임.
  3. 동기화, 백그라운드 업데이트
    • 데이터가 오래되었거나, 다시 필요할 때 자동으로 백그라운드에서 데이터 갱신
  4. 낙관적 업데이트
    • 서버 응답 전 UI에서 먼저 업데이트, 좋은 UX 제공
  5. 성공, 에러, 로딩 상태 관리
    • 기본으로 제공해주므로 쉽게 처리 가능

📌 기본 설정

💻 설치

  • npm i @tanstack/react-query
  • pnpm add @tanstack/react-query
  • yarn add @tanstack/react-query

✍ 사용 전 세팅

  • 리액트쿼리를 사용하기 위해서는 App.tsx, App.jsx, index.tsx, main.tsx, index.js최상위 루트 컴포넌트 또는 엔트리 포인트 에 간단히 추가해줘야 하는 것들이 있다.
  1. QueryClient 생성하기
  2. 전체 앱을 QueryClientProvider에 로 감싸기
  3. QueryClientProvider에 생성한QueryClient 넣기
  • 예시
// main.tsx

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}>
    <StrictMode>
      <App />
    </StrictMode>
  </QueryClientProvider>
);
  • QueryClientProviderQueryClient 인스턴스를 받아서 자식 컴포넌트들이 React Query의 기능(데이터 페칭, 캐싱 등)을 사용할 수 있는 환경을 제공한다.
  • QueryClientReact Query에서 데이터 캐싱, 페칭, 동기화 등의 핵심 로직을 담당하는 객체이다.
  • 자식 컴포넌트들은 QueryClientProvider의 자식이기 때문에 내부에서 React Query의 훅들을 자유롭게 사용할 수 있다.

📌 useQuery

서버에서 데이터를 가져오는(GET) 데 사용되는 Tanstack Query 훅이다.

  • 기본적으로 queryKeyqueryFn을 전달해야 한다.
  • queryKey : 각 쿼리를 식별하기 위해 사용되는 고유한 값이다. 이 값을 기반으로 데이터를 캐싱하고, 필요할 때 다시 불러오거나 재사용한다.
  • queryFn : 서버에서 데이터를 가져오는 역할. 해당 함수하는 promise를 반환하는 비동기 함수여야 한다. useQuery가 데이터를 필요로 할 경우 자동으로 호출된다.

✍ useQuery의 반환값 및 옵션

const {
  data,
  dataUpdatedAt,
  error,
  errorUpdatedAt,
  failureCount,
  failureReason,
  fetchStatus,
  isError,
  isFetched,
  isFetchedAfterMount,
  isFetching,
  isInitialLoading,
  isLoading,
  isLoadingError,
  isPaused,
  isPending,
  isPlaceholderData,
  isRefetchError,
  isRefetching,
  isStale,
  isSuccess,
  isEnabled,
  promise,
  refetch,
  status,
} = useQuery(
  {
    queryKey,
    queryFn,
    gcTime,
    enabled,
    networkMode,
    initialData,
    initialDataUpdatedAt,
    meta,
    notifyOnChangeProps,
    placeholderData,
    queryKeyHashFn,
    refetchInterval,
    refetchIntervalInBackground,
    refetchOnMount,
    refetchOnReconnect,
    refetchOnWindowFocus,
    retry,
    retryOnMount,
    retryDelay,
    select,
    staleTime,
    structuralSharing,
    subscribed,
    throwOnError,
  },
  queryClient,
)
  • 정말 많은 반환값과 옵션이 있지만 자주 사용하는 것들만 정리하자!

📍 data

  • 쿼리가 성공적으로 호출되어서 가져온 데이터
  • 기본값은 undefined이므로 에러가 발생했거나 아직 응답이 완료되지 않았을 때도 undefined이다.
  • 따라서 사용할 때 확인 후 사용하는 것이 좋다. (옵셔널 체이닝 ?. 사용 등)

📍 error

  • 쿼리 실행 중 에러가 발생했을 때 반환되는 에러 객체
  • 해당 객체를 활용하여 각 에러 상황에 적절한 에러 문구를 표시할 수 있다.

📍 isPending (isLoading)

  • v4까지는 isLoading 이었다고 한다.
  • 쿼리가 최초 로딩 중인지를 나타내는 boolean 값이다.
  • true첫 번째 데이터 페칭이 아직 진행 중이고 아직 데이터를 가져오지 않았음을 의미한다.

📍 isFetching

  • 쿼리가 현재 데이터를 가져오고 있는 중인지를 나타내는 boolean 값이다.
  • isPending 과의 차이점은 처음이 아니더라도 데이터 페칭 작업이 진행 중이면 true를 반환한다.

🔥 둘의 차이점

  • isPending : 데이터가 존재하는지? 데이터가 아직 없어서 기다리고 있는 상태. 데이터의 유무에 초점을 둔다.
  • isFetching : 데이터 요청이 진행 중인지? 새로운 요청이 활성화되어 있으면 true. 데이터 요청에 초점을 둔다.

📍 refetch

  • 가져온 데이터를 다시 가져오는 함수
  • 업데이트 버튼 같은 곳에 대한 이벤트 처리에 사용할 수 있다.

🔥 refetch를 실행 시 isPendingisFetching의 차이점

  • 첫 렌더링 시 또는 새로고침 시에는 둘 모두 true가 된다.
  • 이후 refetch 를 함수를 호출하면 isFetchingtrue로 변화하고 isPending은 반응하지 않는다.

📍 error, isError

  • isError로 에러 발생 여부를 확인할 수 있고 error로 해당 에러를 확인할 수 있다.
if (isError) {
	return "에러 발생 : " + error.message; 
}

  • 이 때 위 이미지처럼 기본적으로 에러 발생 이후 3번 더 호출한다.
  • 이는 retry 옵션 때문이다.

📍 retry

  • 실패한 쿼리를 재시도할 지 여부를 나타내는 옵션이다.
  • false이면 fetching 실패 후 재시도를 하지 않는다.
  • true이면 재시도를 무한 반복합니다.
  • 기본값은 3이고 실패 이후 재시도를 3번 반복한다.

📍 retryDelay

  • ms 마다 재시도 (retry)할 지 명시할 수 있는 옵션이다.
  • retry 옵션과 연계된다.

📍 enabled

  • boolean 값을 받는다.
  • false 인 경우 데이터 페칭이 실행되지 않는다.
  • 특정 상황에 따라 api를 호출하고 싶을 때만 하도록 설정할 수 있다.

📍 refetchInterval

  • 지정한 시간마다 주기적으로 쿼리를 자동으로 다시 실행해서 데이터를 최신 상태로 유지하는 옵션이다.
  • 예를 들어, 자동 업데이트 버튼 등에서 사용할 수 있다.

📍 refetchOnWindowFocus

  • 브라우저가 다시 포커스를 얻었을 때 쿼리를 자동으로 refetch하는 옵션이다.
  • 기본값이 true 이다. 무분별한 refetch를 실행하지 않으려면 false로 설정하면 된다.
  • 만약 alwyas로 설정하면 데이터가 최신 상태여도 무조건 refetch한다.

📍 staleTime

  • ms 단위로 줄 수 있다. 해당 시간 만큼 데이터가 fresh 상태로 유지된다.
  • 기본값은 0 이다. 데이터를 불러오자마자 바로 stale 상태가 된다. 이 때는 컴포넌트가 리마운트되거나 브라우저가 포커스되면 항상 데이터를 가져온다.
  • 실시간 성이 중요한 데이터가 아닌 경우, staleTime을 설정하여 네트워크 트래픽을 줄일 수 있다.

📍 gcTime

  • Garbage Collection Time의 약자로 메모리에 있는 캐시된 데이터가 가비지 컬렉터에 의해 자동으로 제거되기 전까지의 시간을 뜻한다.
  • 컴포넌트가 마운트 되면 active 상태가 된다.
  • 이후 언마운트가 되면 해당 컴포넌트에서 사용하던 쿼리는 inactive 상태가 되고 gcTime 카운트가 시작되고 이 시간동안에는 메모리에 데이터가 남아있다.
  • gcTime이 지나기 전에 컴포넌트가 다시 마운트되고 같은 쿼리키가 active 된다면 타이머는 멈추고 데이터가 fresh하다면 네트워크 요청 없이 캐시된 데이터를 반환한다.
  • gcTime이 지나게 되면 해당 쿼리 데이터는 메모리에서 삭제되고 동일한 쿼리키를 요청한다면 네트워크를 통해 데이터를 새로 받아온다.
  • gcTime의 기본값은 5분이다.

🔥 staleTime VS gcTime

  • staleTime : 데이터가 신선한지 아닌지를 관리. fresh한 데이터는 네트워크 요청없이 캐시된 데이터를 재사용한다.
  • gcTime : 메모리에 남아있을지 삭제할지를 관리. 데이터가 캐시에서 삭제되기 까지 걸리는 시간이다.
  • 보통 staleTime보다 gcTime을 같거나 길게 설정한다.

📌 useMutation

서버의 데이터를 변경 (POST, PUT, DELETE) 할 때 사용하는 Tanstack Query 훅이다.

  • useMutation에도 useQuery와 비슷하게 mutationKeymutationFn가 있다.
  • mutationKey : useQuery와는 다르게 서버 데이터 캐싱할 때 쓰이지 않는데 왜 필요할까? 아래와 같은 3가지 이유가 있다고 하는데 추후 자세히 알아보도록 하자.
    • setMutationDefaults / mutation 중앙집중화
    • useMutationState
    • useIsMutation
  • mutationFn : 서버 데이터 변경 시 사용할 함수를 여기에 전달한다.

✍ useMutation의 반환값 및 옵션

const {
  data,
  error,
  isError,
  isIdle,
  isPending,
  isPaused,
  isSuccess,
  failureCount,
  failureReason,
  mutate,
  mutateAsync,
  reset,
  status,
  submittedAt,
  variables,
} = useMutation(
  {
    mutationFn,
    gcTime,
    meta,
    mutationKey,
    networkMode,
    onError,
    onMutate,
    onSettled,
    onSuccess,
    retry,
    retryDelay,
    scope,
    throwOnError,
  },
  queryClient,
)

mutate(variables, {
  onError,
  onSettled,
  onSuccess,
})
  • 역시 정말 많은 반환값과 옵션이 있지만 자주 사용하는 것들만 정리하자!

📍 mutate

  • 해당 함수를 실행하면 mutationFn에 전달한 함수가 실행된다.
  • 이 상태에서 요청은 잘 처리되지만 화면에는 바로 보여지지 않을 수 있다.

📍 onSuccess

  • 요청이 성공했을 때 실행된다.

🔥 화면에 바로 변화를 보여주고 싶다면?

  1. 성공하는 시점 (onSuccess)에 useQueryrefetch 함수를 실행시키는 방법
  2. 쿼리 캐시를 무효화해서 최신 데이터를 가져오도록 하는 방법
    • queryClient 객체의 invalidateQueries를 사용한다.
    • 이 때 무효화시킬 쿼리키를 넣어준다.
      qc.invalidateQueries("post");

=> 둘 중에 보통 두 번째 방법을 많이 사용한다. 쿼리 캐시를 stale로 만들어주기 때문에 자동으로 최신 데이터를 반영하는데 더 효율적이다. refetch는 사용자 액션에 의한 강제 새로고침이 필요한 경우에 사용하는 것이 좋다.

📍 onError

  • 요청이 실패했을 때 실행된다.

📍 isPending

  • useQuery 와 동일하게 반환한다.

📍 onSettled

  • 성공, 실패 여부와 관계없이 요청 이후 항상 실행된다.
  • 항상 요청 이후 초기화 되는 로직이 필요할 때 사용한다.

😄 마치며

Tanstack Query에 대해 간단히 정리해보았다. 이전에 정리할 당시에는 사용해보지 않고 정리했던 것이라 머리로만 이해하고 넘어갔었는데 사용해보며 작성하니 확실히 이해가 더 잘 되는 것 같다.

더 다양한 기능이 많아 사용해보며 필요한 것들을 추가해야 할 것 같다. 다음에는 간단히 useInfiniteQuery를 활용한 무한 스크롤에 대해 정리해보겠다.

profile
지금이라도 시작해보자..!

1개의 댓글

comment-user-thumbnail
2025년 10월 27일

사용해보니 더욱 이해가 잘 된다니 너무 좋습니다!

답글 달기