C.W.K.
Stream
Lesson 06 of 07 · published

TanStack Query — 짧은 터치포인트

~11 min · tanstack-query, state-management, ecosystem

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
TanStack Query 는 앱에 같은 페치 데이터 공유하는 컴포넌트 둘 이상 생기면 모두가 손 뻗는 server-state 라이브러리. 뭘 푸는지 + '쓰지' vs '직접 짜기' 의 선이 어디 있는지 아는 게 이 퀘스트엔 충분.

TanStack Query 가 뭐

Fetcher 감싸고 캐시, 동시 요청 dedup, 백그라운드 refetch, stale-while-revalidate 신선도, 재시도 로직, Suspense-호환 API 추가하는 라이브러리. Fetcher 함수 본인이 작성; 라이브러리가 운영 관심사 처리.

모양

const { data, isLoading, error } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});

여러 컴포넌트가 같은 queryKeyuseQuery 호출 가능 — fetch 한 번만 발화, 모든 컴포넌트가 같은 데이터, 캐시 일관성 유지.

Suspense 모드

suspense: true 추가하면 hook 이 use()-모양: isLoading 반환 대신 suspend. Suspense + error boundary 와 결합 → 컴포넌트 body 가 success 경로뿐. 양쪽 최선: TanStack Query 의 캐시 혜택 + Suspense 의 선언적 로딩/에러.

손이 갈 때

  • 같은 데이터 페치하는 컴포넌트 둘 이상 (sidebar + 메인 패널 둘 다 user 객체 필요).
  • Optimistic 업데이트, 재시도, window focus refetch, polling, infinite scroll 필요.
  • 리스트 페이지 → 디테일 페이지 네비게이션 + 리스트 캐시가 re-fetch 없이 디테일 채우길 원함.

안 필요할 때

  • 공유나 revalidation 관심사 없는 one-off 페치.
  • WebSocket / SSE 로 바뀌는 서버 구동 state (real-time-모양 라이브러리 또는 커스텀 hook).
  • 데이터가 모듈 레벨 상수인 순수 SPA 부트스트래핑.
운영 관심사가 진짜 작업일 때 라이브러리 써. TanStack Query 는 'fetch 에 추가 단계' 가 아냐 — 캐시, 재시도, dedup, refetch 동작에 대한 추상 — 안 그러면 hook 들에 (나쁘게) 재구현하게 될 것. 본인 캐시 map 짜기 시작하는 순간 갈아타.

Code

Suspense 모드의 기본 useQuery·tsx
import { QueryClient, QueryClientProvider, useSuspenseQuery } from "@tanstack/react-query";
import { Suspense } from "react";

const queryClient = new QueryClient();

function User({ userId }: { userId: string }) {
  // Suspense-호환 — destructure 에 isLoading/error 없음.
  const { data: user } = useSuspenseQuery({
    queryKey: ["user", userId],
    queryFn: () => fetch(`/api/users/${userId}`).then((r) => r.json()),
  });
  return <p>{user.name}</p>;
}

export function App({ userId }: { userId: string }) {
  return (
    <QueryClientProvider client={queryClient}>
      <Suspense fallback={<p>Loading…</p>}>
        <User userId={userId} />
      </Suspense>
    </QueryClientProvider>
  );
}
Optimistic 업데이트 가진 mutation — TanStack Query 강점·tsx
import { useMutation, useQueryClient } from "@tanstack/react-query";

function useRenameConversation() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({ id, title }: { id: string; title: string }) =>
      fetch(`/api/conversations/${id}/title`, {
        method: "PATCH",
        body: JSON.stringify({ title }),
      }).then((r) => r.json()),
    onMutate: async ({ id, title }) => {
      // Optimistic 업데이트 — UI 가 새 title 즉시 표시.
      await queryClient.cancelQueries({ queryKey: ["conversation", id] });
      const previous = queryClient.getQueryData(["conversation", id]);
      queryClient.setQueryData(["conversation", id], (old: { title: string }) => ({
        ...old,
        title,
      }));
      return { previous, id };
    },
    onError: (_e, _vars, ctx) => {
      // Fail 시 roll back.
      if (ctx) queryClient.setQueryData(["conversation", ctx.id], ctx.previous);
    },
    onSettled: (_d, _e, { id }) => {
      // 서버와 재동기화.
      queryClient.invalidateQueries({ queryKey: ["conversation", id] });
    },
  });
}

External links

Exercise

@tanstack/react-query 설치. 앱 root 에 QueryClient 셋업. useEffect + useState 로 페치하는 컴포넌트를 useSuspenseQuery 쓰는 걸로 리팩토링. 확인: (1) 컴포넌트 더 짧음; (2) 같은 query key 가진 컴포넌트 인스턴스 둘이 네트워크 요청 하나만 트리거; (3) 떠났다 돌아와도 re-fetch 안 함 (기본 stale time 안에서).
Hint
DevTools 의 Network 패널 열어. useState 패턴이면 새 userId 의 매 render 가 fetch 트리거. useSuspenseQuery 면 첫 fetch 캐시; 같은 key 의 후속 방문이 캐시 hit.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.