[TanStack Query] useQuery 기본 사용법 - 파라미터와 반환값 이해하기

2026. 4. 14. 16:03·libraries/TanStack Query

useQuery 기본 사용법

useQuery는 TanStack Query의 핵심 훅 중 하나로 서버 데이터를 가져오고, 캐싱하고, 동기화 및 업데이트하는 전체 라이프 사이클을 캡슐화해준다.

useQuery의 파라미터

const result = useQuery({
  queryKey,  // required
  queryFn,   // required
  // ...options ex) staleTime, gcTime, enabled, select, ...
});

인자로는 하나의 객체를 받으며, queryKey와 queryFn 두 필드는 필수이다.

queryKey: 데이터 고유 식별자

queryKey는 서버 데이터의 고유 주소 라벨이라고 생각하면 된다. 배열 형태로 작성하며, TanStack Query는 이를 다음 세 가지 용도로 활용한다.

1. 캐시 키: 특정 queryKey로 데이터를 가져오면 TanStack Query는 해당 데이터를 저장한다. 동일한 queryKey로 다시 데이터를 요청하면 캐시된 데이터를 즉시 반환한다.
2. 데이터 갱신: 데이터를 업데이트한 후 해당 queryKey를 무효화(invalidate) 하면 TanStack Query가 서버에서 새로 데이터를 가져온다.
3. 의존성 식별: 데이터가 변수(userId, 검색어 등)에 의존한다면, 그 변수를 queryKey 배열에 포함시킨다. 변수가 다르면 서로 다른 데이터이므로, 별도로 캐싱되고 관리된다.

// 모든 할 일 목록
useQuery({ queryKey: ['todos'], queryFn: fetchTodos })

// 특정 할 일 (todoId가 바뀌면 새로운 쿼리-다른 데이터로 취급됨)
useQuery({ queryKey: ['todo', todoId], queryFn: () => fetchTodo(todoId) })

// 필터가 포함된 목록
useQuery({ queryKey: ['todos', { status: 'done' }], queryFn: fetchFilteredTodos })

queryKey는 안정적이고 데이터의 고유 식별자역할을 할 수 있어야 한다. queryKey를 어떻게 설계하느냐에 따라 캐시 관리의 복잡도가 달라지게 된다! 이 부분은 다음 편에서 자세히 다뤄보겠다.

queryFn: 데이터를 가져오는 방법

queryFn은 특정 queryKey와 연관된 데이터를 어떻게 가져올지 TanStack Query에게 알려주는 함수이다. 반드시 Promise를 반환해야 하며, 성공 시 데이터를 resolve, 실패 시 에러를 throw해야 한다. 여기서 보통 API를 호출한다(fetch 또는 axios)

// fetch 사용 시 — HTTP 에러를 직접 처리해야 함
const fetchTodos = async () => {
  const response = await fetch('https://api/todos');
  if (!response.ok) {
    // fetch는 404, 500 같은 HTTP 에러에서 자동으로 throw하지 않음
    throw new Error('Failed to fetch todos');
  }
  return response.json();
};

// axios 사용 시 — HTTP 에러를 자동으로 throw
const fetchTodos = async () => {
  const { data } = await axios.get('/todos');
  return data;
};

fetch는 네트워크 오류가 아닌 HTTP 에러(404, 500 등)에서 자동으로 throw하지 않는다. 때문에 if (!response.ok) 체크가 반드시 필요하다. axios는 이를 자동으로 처리해주기 때문에 queryFn이 더 간결해진다.

useQuery의 반환값

공식문서

useQuery는 단순히 패치해온 데이터만 반환하는게 아니라, 쿼리의 현재 상태를 나타내는 다양한 값들을 반환한다. 이 값들을 제대로 이해하고 사용해야 올바를 UI를 구성할 수 있다.

const {
  data,       // 패칭된 데이터 (성공 시에만 존재)
  error,      // 에러 객체 (에러 발생 시에만 존재)
  status,     // 'pending' | 'error' | 'success'

  isPending,  // 아직 캐시된 데이터가 없는 상태
  isError,    // 에러 발생 상태
  isSuccess,  // 데이터 정상 수신 상태

  isFetching, // 백그라운드 포함, 현재 fetch가 진행 중이면 true
  isLoading,  // isPending && isFetching — 최초 로딩 중
} = useQuery({ queryKey, queryFn });

status: 데이터에 대한 상태값

useQuery가 반환하는 핵심적인 값 중에 status라는 값이 있다.
status는 "현재 이 쿼리에 데이터가 있냐 없냐" 를 기준으로 쿼리의 상태를 나타내는 값이다. pending, error, success 세 가지 값만 가진다.

const {
  status,     // 'pending' | 'error' | 'success'
} = useQuery({ queryKey, queryFn });
  • pending: 아직 데이터가 없는 상태를 나타내며, 두 가지 경우에 해당된다.
    • 캐시된 데이터가 없고, 아직 fetch 시도도 안한 경우
    • 캐시된 데이터가 없고, fetch를 시도했지만 아직 완료되지 않은 경우

"데이터가 없다" 는게 핵심이며, fetch 중인지 여부는 status로 판단할 수 없다!

  • error: fetch 시도가 에러로 끝난 상태를 나타낸다.
    • 이 때 error 프로퍼티에는 실제 에러 객체가 담겨있게 된다.
  • success: 데이터를 정상적으로 받은 상태를 나타낸다.
    • 이 때 data 프로퍼티에 실제 데이터가 담겨 있게 된다. enabled: false로 설정되어 fetch를 한 번도 안 했더라도 initialData를 설정해뒀다면 처음부터 success 상태가 된다!

status를 통해서는 "데이터(캐시)가 존재하는가?" 를 알 수 있다.

fetchStatus: 네트워크 요청에 대한 상태값

fetch 진행 여부는 status가 아닌 fetchStatus를 통해 확인할 수 있다. fetchStatus는 지금 queryFn이 실제로 실행 중인지를 나타낸다. status와 마찬가지로 세 가지 값만 가지며 fetching, paused, idle을 갖는다.

const {
  fetchStatus,     // 'fetching' | 'paused' | 'idle'
} = useQuery({ queryKey, queryFn });
  • fetching: queryFn이 실행중인 상태를 나타낸다.
    • 최초 로딩이든 백그라운드 refetch든 queryFn이 실행되고 있으면 fetching상태가 된다!
  • paused: fetch 하려 했는데 멈춘 상태를 말한다.
    • 가장 흔한 케이스는 오프라인 상태이다.TanStack Query의 networkMode 설정과 연관이 있다. (기본값 online — 오프라인이면 fetch를 paused 상태로 대기)
  • idle: fetch하지 않는 상태를 말한다.
    • queryFn이 실행되지 않고 있는 평상시 상태를 나타낸다.

파생 상태값

status 파생값

  • 편의를 위한 파생값이다.
isPending === (status === 'pending')           // 데이터 없음
isSuccess === (status === 'success')           // 데이터 있음 
isError   === (status === 'error')             // 에러 상태

fetchStatus 파생값

  • 편의를 위한 파생값이다.
isFetching === (fetchStatus === 'fetching')    // queryFn 실행 중
isPaused   === (fetchStatus === 'paused')      // fetch 중단됨

status + fetchStatus 조합으로 파생되는 값들

isLoading = isFetching && isPending        // 최초 fetch가 진행 중인 상태 (데이터없음 + fetching)
// = "캐시도 없고(isPending) + 지금 fetch 중(isFetching)"
isRefetching = isFetching && !isPending    // 백그라운드 refetch가 진행 중인 상태 (데이터있음 + fetching)
// = "fetch 중(isFetching) + 캐시는 있음(!isPending)"

에러 관련 세분화 값들

에러 상태를 더 세밀하게 구분하는 값들도 있다.

jsisLoadingError  // 최초 fetch 도중 에러가 발생한 경우 true
isRefetchError  // refetch 도중 에러가 발생한 경우 true

에러 원인을 추적할 때 유용한 값들은 다음과 같다.

jsfailureCount   // fetch 실패 횟수. 성공 시 0으로 리셋
failureReason  // 재시도 실패 이유. 성공 시 null로 리셋
errorUpdateCount // 누적 에러 발생 횟수 (리셋 없음)

데이터와 관련된 값들

data           // 가장 최근에 성공적으로 받은 데이터. 기본값 undefined
dataUpdatedAt  // 마지막으로 success 상태가 된 시각 (timestamp)
error          // 에러 객체. 기본값 null
errorUpdatedAt // 마지막으로 error 상태가 된 시각 (timestamp)

함수

refetch(options?)
// 쿼리를 수동으로 다시 패칭하는 함수
// 에러 발생 시 기본적으로 로그만 남김
// 에러를 throw하려면 throwOnError: true 옵션 전달

isPending vs isLoading

공식문서에서는 대부분 쿼리에서 isPending(데이터 없음) → isError(에러) → data(성공 상태로 간주) 순으로 체크하면 모든 케이스를 깔끔하게 커버할 수 있다고 언급하고 있다.

그렇다면 isLoading말고 왜 isPending을 사용하도록 권장하는 걸까?

TypeScript의 타입 narrowing

isPending은 status 필드에서 직접 파생된 값인 반면에, isLoading은 status = 'pending'과 fetchStatus = 'fetching의 조합이다. 따라서 isLoading과 isError가 둘 다 false여도 data가 있다고 보장할 수 없다.
반면, isPending과 isError가 둘 다 false면 TypeScript가 data에서 undefined를 자동으로 제거해주게 된다.

// isLoading 사용 시
const { isLoading, isError, data } = useQuery(...)
if (isLoading) return <Loading />
if (isError) return <Error />
return <div>{data?.title}</div>  // data가 undefined일 수 있어서 ?. 필요

// isPending 사용 시
const { isPending, isError, data } = useQuery(...)
if (isPending) return <Loading />
if (isError) return <Error />
return <div>{data.title}</div>   // data가 반드시 존재함이 보장됨, ?. 불필요

GitHub Discussion

'libraries > TanStack Query' 카테고리의 다른 글

[TanStack Query] 서버 상태 이해하기 + TanStack Query 시작하기  (0) 2026.04.13
'libraries/TanStack Query' 카테고리의 다른 글
  • [TanStack Query] 서버 상태 이해하기 + TanStack Query 시작하기
뜐🐸
뜐🐸
패왕색 패기를 갖춘 뜐입니다~
  • 뜐🐸
    뜐의 개발 로그
    뜐🐸
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 기초 학습
        • HTML
        • CSS
        • JavaScript
        • Version Co..
        • 미니 프로젝트
        • DOM & 웹 AP..
      • CSS 프레임워크
        • Bootstrap
      • React
        • 개념 정리
        • 기초 정리
      • 알고리즘
        • Week 1: 입출..
        • 재귀
        • 백트래킹
      • javascript
      • FastAPI
        • 크롤링 서버 만들기
      • 전역 상태 관리
        • Redux
      • 한 입 리액트 챌린..
      • 영어
      • libraries
        • TanStack Q..
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    :focus
    가상 선택자
    자바스크립트 #하노이 탑 #재귀 #백준 # 11729
    :active
    티스토리챌린지
    inline-block
    :hover
    :nth-child(n)
    백준 #코딩테스트 #1074번 #재귀 #알고리즘 # 알고리즘 문제풀이
    오블완
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
뜐🐸
[TanStack Query] useQuery 기본 사용법 - 파라미터와 반환값 이해하기
상단으로

티스토리툴바