[React] TanStack Query(react query) 리액트쿼리

ByEunwoo
react

pic

react-query는 상당히 많이 사용되는 상태관리 라이브러리 중 하나이다.

**TanStack Query(FKA React Query)**는 종종 웹 애플리케이션용 누락된 데이터 가져오기 라이브러리로 설명되지만 좀 더 기술적인 용어로 말하면 웹 애플리케이션에서 서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 매우 쉽게 만든다.

> 「kakao 2021 - 카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유」 세줄요약

React Query는 React Application에서 서버 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트하는 작업을 도와주는 라이브러리이다.
복잡하고 장황한 코드가 필요한 다른 데이터 불러오기 방식과 달리 React Component 내부에서 간단하고 직관적으로 API를 사용할 수 있다.
더 나아가 React Query에서 제공하는 캐싱, Window Focus Refetching 등 다양한 기능을 활용하여 API 요청과 관련된 번잡한 작업 없이 “핵심 로직”에 집중할 수 있다.

즉, API요청과 관련된 값을 불러오고, 캐싱하고, 시간이 지난 데이터들을 업데이트하는 것을 담당하는 라이브러리


초기세팅

일단 react-query를 사용하기 위해서 library 설치를 해야한다.

npm i @tanstack/react-query

index.tsx or main.tsx

import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
 
const queryClient = new QueryClient();
 
createRoot(document.getElementById('root')!).render(
  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOpen={false} buttonPosition='bottom-right' />
  </QueryClientProvider>,
);

React Query는 서버 상태를 캐싱하고 관리한다.
QueryClient는 React Query의 핵심 인스턴스로, 모든 서버 상태 관리를 담당한다.
이 인스턴스를 앱 전체에서 공유하게 된다.

<QueryClientProvider>는 React Query 클라이언트를 앱 전체에 주입합니다. 이 안에 있어야 useQuery, useMutation 등이 정상 동작합니다.
<ReactQueryDevtools>는 개발 중 유용한 디버깅 도구로, 쿼리 상태를 시각적으로 확인하고 디버깅할 수 있게 해준다. 현재 캐시된 쿼리, 상태, 오류 등을 시각적으로 확인할 수 있다. initialIsOpen=는 시작 시 Devtools를 닫아둔다는 의미이다.


useQuery 사용법

import { useQuery } from "@tanstack/react-query";
import axios from "axios";
 
export default function ReactQuery() {
  const fetchPost = () => {
    return axios.get("http://localhost:3004/posts");
  };
 
  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: ["posts"],
    queryFn: fetchPost,
    retry: 1,
 
    select: (data) => {
      return data.data;
    },
    staleTime: 10000, // 1분간 api호출 금지
    // gcTime: 10000, // 10초간 캐시가 유지      staleTime < gcTime 이어야 한다.
    // refetchInterval: 3000,   // 3초 마다 api 호출함
    // refetchOnMount: false, // 처음에는 fetch되지만 다시 page를 들어갈땐 fetch를 더 이상 안함 , 반대로 true하면 매번 호출 (기본값)
    // refetchOnWindowFocus: true, // 실시간으로 항상 refetch 됌  -> 화면 볼때마다
  });
 
  console.log("데이터 테스트: ", data, isLoading);
  console.log("에러 테스트: ", isError, error);
 
  if (isLoading) {
    return <h1>Loading...</h1>;
  }
 
  if (isError) {
    return <h1>{error.message}</h1>;
  }
 
  return (
    <main>
      <button
        onClick={() => refetch()}
        className="bg-red-500 p-1 text-white rounded-lg"
      >
        post리스트 다시 들고오기!
      </button>
      {data.map((item: any, idx: number) => (
        <div key={idx}>{item.title}</div>
      ))}
    </main>
  );
}
 

useQuery 기본 개념

useQuery는 데이터 패칭, 캐싱, 재시도 등의 로직을 간단하게 처리하는 React Query의 훅이다. 이 훅을 사용하여 서버에서 데이터를 가져오고, 로딩 상태, 에러 상태 등을 쉽게 관리할 수 있다.

const { data, isLoading, error } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPost,
  retry: 1,
  select: (data) => data.data,
  staleTime: 10000,
});
  • queryKey: 쿼리의 고유한 식별자 (캐싱의 기준)

    • 캐시와 자동 재요청 여부 판단의 기준이 됨
    • 배열 형태로 넘기며, 파라미터를 포함할 수도 있음 → 예: ["post", id]
  • queryFn: 데이터를 가져오는 비동기 함수

    • 반드시 Promise를 반환해야 함
  • retry: 실패 시 재시도 횟수 설정 (기본값은 3)

    • false로 설정하면 재시도 없음
  • select: 성공한 데이터를 원하는 형태로 변환

    • 예: 서버 응답이 { data: [...] } 구조일 때, data.data만 꺼내기
  • staleTime: 데이터를 "신선한" 상태로 유지하는 시간 (밀리초 단위)

    • 이 시간 동안은 자동 재요청이 발생하지 않음
    • 예: 10000 → 10초 동안 캐시된 데이터를 사용

staleTime의 역할

항목설명
데이터 신선도 유지staleTime 안에서는 캐시 사용, 네트워크 요청 없음
재요청 최적화staleTime이 지나면 stale 처리되고, 필요 시 재요청됨
사용자 경험 개선똑같은 데이터를 매번 다시 요청하지 않음

❗ 주의: staleTime은 네트워크 요청을 막는 것이 아니라 자동 재요청을 제어하는 것이다.

현재 코드에서는 staleTime: 10000 이므로 1분간 api호출 금지를 의미한다.

자주 쓰이는 추가 옵션

옵션설명
gcTime: 10000캐시가 가비지 컬렉션 되기 전까지 유지되는 시간 (단위: ms) , 일반적으로 gcTime > staleTime 이어야 함
refetchInterval: 30003초마다 자동으로 API 재요청 (Polling 용도로 사용)
refetchOnMount: false컴포넌트가 다시 마운트될 때 자동 fetch 하지 않음 , true면 매번 fetch
refetchOnWindowFocus: true창(탭)에 포커스될 때마다 데이터 자동으로 refetch됨 , 실시간 데이터에 유용

또한 useQuery함수에서 "refetch"를 들고와서 button 태그에서 onClick핸들러에 fetch함수를 넣어서 매번 클릭할 때마다 fetch 즉 api호출을 할 수 있다.

pic

수동으로 refetch 하기

React Query는 자동화에 강하지만, 수동으로도 제어할 수 있다.

const { data, refetch } = useQuery({ ... });
 
return (
  <button onClick={() => refetch()}>
    새로고침
  </button>
);
 
  • refetch()는 언제든지 수동으로 API를 다시 호출한다.
  • 버튼 클릭, 이벤트 발생 시 등 유연하게 사용 가능하다.

useQueries 사용법

React Query에서는 보통 하나의 데이터를 가져올 때 useQuery 훅을 사용한다. 하지만 여러 개의 API 요청을 동시에 병렬적으로 처리해야 하는 경우도 많다.
이럴 때 유용하게 쓰이는 훅이 바로 useQueries입니다.

언제 useQueries를 써야 할까?

  • ID가 여러 개인 게시글 상세 데이터를 동시에 가져올 때
  • 여러 개의 API를 병렬로 호출해야 할 때
  • 각각의 요청을 따로 useQuery로 작성하기 귀찮을 때

useQueries 기본 사용법

import { useQueries } from '@tanstack/react-query';
import axios from 'axios';
 
export default function ReactQueriesPage() {
  const ids = [1, 2, 3, 4];
 
  const fetchPostDetail = (id: number) => {
    return axios.get(`http://localhost:3004/posts/${id}`);
  };
 
  const result = useQueries({
    queries: ids.map((id) => {
      return {
        queryKey: ['posts', id],
        queryFn: () => fetchPostDetail(id),
      };
    }),
 
    combine: (results) => {
      return {
        data: results.map((result) => result.data?.data),
      };
    },
  });
 
  console.log('결과', result);
 
  return (
    <main>
      <div>{}</div>
    </main>
  );
}

useMutation 사용법

useQuery는 주로 GET 요청을 처리할 때 사용한다.
반면, POST / PUT / DELETE 등 변경 요청을 처리할 때는 useMutation 훅을 사용한다.

useMutation은 서버의 데이터를 변경하는 작업(생성, 수정, 삭제 등)에 적합합니다.

useMutation 예제 – POST 요청

// hooks/mutations/useCreatePost.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createPost, PostInput } from '@/apis/postApi';
 
export const useCreatePost = () => {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
    onError: (error) => {
      console.error('에러 발생:', error);
      // toast.error("작성 실패!") 등
    },
    onSettled: () => {
      // 성공이든 실패든 마지막에 무조건 실행
    },
  });
};
  • 사용 예: PostForm.tsx
// components/PostForm.tsx
import { useCreatePost } from '@/hooks/mutations/useCreatePost';
import { useState } from 'react';
 
export default function PostForm() {
  const { mutate, isPending, isSuccess } = useCreatePost();
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
 
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    mutate({ title, content });
  };
 
  return (
    <form onSubmit={handleSubmit}>
      <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder='제목' />
      <textarea value={content} onChange={(e) => setContent(e.target.value)} placeholder='내용' />
      <button type='submit' disabled={isPending}>
        작성하기
      </button>
      {isSuccess && <p>글 작성 완료!</p>}
    </form>
  );
}

마무리

이번 글에서는 **TanStack Query (React Query)**의 핵심 훅인 useQuery와 useMutation을 각각 살펴보았다.

  • 🔹 useQuery: 데이터를 가져오는(GET) 데 사용하는 훅
    → 캐싱, 로딩 상태, 재요청, 자동 갱신 등 다양한 기능을 기본으로 제공

  • 🔸 useMutation: 데이터를 변경하는(POST/PUT/DELETE) 데 사용하는 훅
    → 서버 요청 후 후처리, 에러 핸들링, 캐시 무효화 등을 유연하게 처리 가능

tanstack query(react-query)를 사용하여 이 두 훅을 상황에 맞게 잘 조합하면 복잡한 비동기 로직도 훨씬 깔끔하고 선언적으로 구현할 수 있다.

Posted inreact
Written byEunwoo