01 · Concept

React Query (TanStack Query)

서버 상태를 선언적으로 관리하는 라이브러리로, 캐싱·동기화·백그라운드 업데이트를 자동으로 처리합니다.

React Query는 서버 상태(Server State) 클라이언트 상태(Client State)를 분리합니다.useQuery로 데이터를 fetch하면 자동으로 캐싱·로딩·에러 상태가 관리됩니다. staleTime은 데이터를 fresh로 간주하는 시간이고, gcTime은 비활성 캐시를 메모리에서 제거하기까지의 시간입니다. queryKey는 캐시의 식별자로, 계층적 배열로 설계해야 invalidateQueries를 세밀하게 제어할 수 있습니다.

02 · Interactive Demo

React Query 동작 시각화

쿼리 상태 흐름, staleTime/gcTime 타이머, Optimistic Update 롤백을 직접 확인하세요.

Query State Machine
idle

쿼리가 아직 실행되지 않은 초기 상태

loading

서버에 첫 요청을 보내는 중 (캐시 없음)

success (fresh)

데이터 수신 완료. staleTime 동안 fresh 상태 유지

stale

staleTime 경과. 다음 포커스/마운트 시 백그라운드 재요청

refetching

stale 데이터를 보여주면서 백그라운드에서 새 데이터 요청 중

03 · Code Example

코드 예시

실제 코드로 동작 원리를 확인하세요.

import { useQuery } from '@tanstack/react-query';

// queryKey는 캐시 식별자 — 계층적 배열로 설계
const { data, isLoading, isError, isFetching } = useQuery({
  queryKey: ['properties', { city: 'seoul', type: 'apt' }],
  queryFn: () => fetchProperties({ city: 'seoul', type: 'apt' }),
  staleTime: 1000 * 60 * 5,   // 5분 동안 fresh
  gcTime: 1000 * 60 * 10,     // 비활성 10분 후 캐시 제거
  enabled: !!userId,           // 조건부 실행
});

// isLoading: 캐시 없이 처음 로딩 중
// isFetching: 백그라운드 재요청 포함 모든 요청 중
04 · Interview Point

면접 핵심 질문

staleTime은 데이터를 fresh로 간주하는 시간입니다. 이 시간 내에는 동일한 queryKey로 useQuery를 마운트해도 재요청을 보내지 않고 캐시를 즉시 반환합니다. 기본값은 0(즉시 stale)입니다.

gcTime은 컴포넌트가 언마운트되어 쿼리가 비활성이 된 후 캐시를 메모리에서 제거하기까지의 시간입니다. 기본값은 5분입니다. gcTime이 남아있는 동안 같은 쿼리를 다시 마운트하면 stale 데이터를 즉시 보여주면서 백그라운드 재요청이 시작됩니다.
계층적 배열로 설계합니다. 예를 들어 ['properties', userId, { type: 'apt' }]처럼 엔티티 → 식별자 → 필터 순으로 구성합니다.

이렇게 하면 invalidateQueries({ queryKey: ['properties'] })로 모든 properties 쿼리를 무효화하거나, invalidateQueries({ queryKey: ['properties', userId] })로 특정 유저의 properties만 무효화하는 세밀한 제어가 가능합니다. 객체나 배열을 포함해도 deep equality로 비교하므로 순서가 중요합니다.
onMutate에서 현재 캐시 스냅샷을 저장하고 즉시 UI를 업데이트합니다. onError에서 스냅샷으로 캐시를 복원하고, onSettled에서 성공·실패 무관하게 invalidateQueries로 서버 최신 데이터를 동기화합니다.

주의할 점은 onMutate 시작 시 cancelQueries를 먼저 호출해야 한다는 것입니다. 진행 중인 refetch가 Optimistic 업데이트를 덮어쓰는 경쟁 조건(race condition)을 방지하기 위해서입니다.
useQuery { suspense: true } 옵션을 주거나, v5에서는 useSuspenseQuery를 사용합니다. 컴포넌트를 <Suspense fallback={<Spinner />}>로 감싸면 로딩 중에 fallback을 보여주고, 데이터가 준비되면 컴포넌트를 렌더링합니다. 에러는 상위의 <ErrorBoundary>가 잡습니다. 이 패턴으로 isLoading/isError 분기 없이 선언적으로 비동기 UI를 구성할 수 있습니다.