
프론트엔드 개발을 하다 보면 API 데이터를 가져오기 위해 fetch나 axios를 사용한다. 하지만 단순히 데이터를 가져오는 것만으로는 실제 서비스에서 발생하는 다양한 문제를 해결하기 어렵다.
예를 들어 다음과 같은 고민이 생긴다.
- 로딩 상태는 어떻게 관리할까?
- 에러가 발생하면 어떻게 처리할까?
- 같은 데이터를 여러 번 요청하지 않으려면?
- 화면을 다시 방문했을 때 데이터를 재사용할 수 있을까?
- 백그라운드에서 최신 데이터로 자동 갱신할 수 있을까?
이러한 서버 상태(Server State) 관리를 쉽게 해주는 라이브러리가 바로 TanStack Query이다.
서버 상태(Server State)란?
서버 상태
서버에서 가져온 데이터이다
- 사용자 정보
- 게시글 목록
기존 방식
React에서 데이터를 가져올 때 보통 이렇게 작성한다.
import axios from "axios";
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
axios
.get("/api/users")
.then((response) => {
setUsers(response.data);
setLoading(false);
});
}, []);
처음에는 간단하지만 프로젝트가 커질수록 문제가 발생한다.
- 로딩 상태 관리
- 에러 처리
- 중복 요청 방지
- 캐싱
- 데이터 갱신
모두 직접 구현해야 한다.
아래는 데이터 저장과 로딩 상태 관리 에러처리만 되어있는 코드이다
import axios from "axios";
const [users, setUsers] = useState([]);// 사용자 데이터 저장
const [loading, setLoading] = useState(true);// API 요청 진행 여부 저장
const [error, setError] = useState(null);// API 요청 실패 시 에러 저장
useEffect(() => {
const fetchUsers = async () => {
try {
const response = await axios.get("/api/users");
setUsers(response.data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
여기에 캐싱, 중복 요청 방지, 자동 재시도, 데이터 최신화 기능까지 추가된다면 코드와 관리 포인트는 더욱 늘어나게 된다.
TanStack Query는 이러한 서버 상태 관리를 추상화하여 개발자가 비즈니스 로직에 집중할 수 있도록 돕는다.
TanStack Query 사용 예시
몇 줄만으로 아래 내용이 자동으로 처리된다.
- 데이터 요청
- 로딩 상태
- 에러 처리
- 캐싱
const {
data: users,
isLoading,
error,
} = useQuery({
queryKey: ["users"],
queryFn: async () => {
const response = await axios.get("/api/users");
return response.data;
},
});
TanStack Query 핵심 기능
1. 캐싱(Cache)
한 번 가져온 데이터를 저장해 둔다.
queryKey: ["users"];
같은 Query Key로 요청하면 캐시 된 데이터를 사용한다.
※Query Key의 역할
queryKey는 단순히 캐시를 찾기 위한 값이 아닌 서버 데이터를 구분하는 고유 식별자 역할을 한다.
캐시 저장, 조회, 무효화, 자동 리페치등 모든 서버 상태 관리의 기준이 된다.
2. 자동 리페치(Refetch)
TanStack Query는 캐시된 데이터를 무조건 계속 사용하는 것이 아니라,
필요한 시점에 서버 데이터를 다시 요청하여 최신 상태로 유지도 한다.
예를 들어 사용자가 다른 탭을 확인한 후 다시 돌아오거나,
페이지를 다시 방문하는 경우 자동으로 데이터를 다시 가져올 수 있다.(옵션 찾아보기><)
3. 백그라운드 업데이트
사용자는 기존 데이터를 보고 있고
백그라운드에서는 최신 데이터를 가져온다.
UX가 훨씬 좋아진다.
4. 로딩 상태 관리
const { isLoading } = useQuery(...);
직접 상태를 만들 필요가 없다.
5. 에러 처리
const { error } = useQuery(...);
API 실패 여부를 쉽게 확인할 수 있다.
TanStack Query는 크게 두 가지로 나뉜다.
Query → 조회(Read)
Mutation → 변경(Create, Update, Delete)
Query
서버 데이터를 가져올 때
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
- 회원 목록 조회
Mutation
서버 데이터를 변경할 때
const mutation = useMutation({
mutationFn: createUser,
});
- 회원 생성
- 회원 수정
- 회원 삭제
회원 추가 예시
import { useState } from "react";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
const createUser = async (user) => {
const response = await axios.post("/api/users", user);
return response.data;
};
function UserForm() {
const [name, setName] = useState("");
const mutation = useMutation({
mutationFn: createUser,
});
const handleSubmit = () => {
mutation.mutate({
name,
});
};
return (
<>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button onClick={handleSubmit}>
저장
</button>
</>
);
}
회원 삭제 예시
const deleteUser = async (id) => {
return axios.delete(`/api/users/${id}`);
};
const mutation = useMutation({
mutationFn: deleteUser,
});
실행
mutation.mutate(1);
그런데 여기서 중요한 문제가 생긴다.
회원 목록 조회
↓
캐시 저장
회원 삭제 성공
↓
캐시는 아직 옛날 데이터
화면에 삭제된 회원이 남아있다.
그래서 Mutation은 보통 Query와 같이 쓴다.
const queryClient = useQueryClient();
const deleteMutation = useMutation({
mutationFn: deleteUser,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["users"],
});
},
});
※ invalidateQueries
Mutation으로 서버 데이터가 변경되면 기존 Query 캐시는 최신 상태가 아닐 수 있다.
이때 invalidateQueries를 사용해 관련 Query를 다시 가져오도록 한다.
추가로 알아두면 좋은 옵션
TanStack Query는 기본 설정만으로도 많은 기능을 제공하지만, 실제 프로젝트에서는 데이터 특성에 맞게 옵션을 조정하는 경우가 많다.
대표적으로 알아둘 옵션은 enabled, select, staleTime, gcTime이다.
enabled
enabled는 Query 실행 여부를 제어하는 옵션이다.
기본적으로 useQuery는 컴포넌트가 실행되면 바로 API 요청을 보낸다.
하지만 특정 조건이 만족될 때만 데이터를 가져와야 하는 경우가 있다.
예를 들어 사용자 ID가 있어야 사용자 정보를 조회할 수 있는 경우:
useQuery({
queryKey: ["user", userId],
queryFn: fetchUser,
enabled: !!userId,
});
동작 방식:
userId 없음
↓
API 요청하지 않음
userId 존재
↓
Query 실행
↓
데이터 조회
로그인 이후 사용자 정보 조회, 검색 조건이 입력된 후 조회하는 상황에서 자주 사용한다.
select
select는 서버에서 받아온 데이터를 원하는 형태로 변환하는 옵션이다.
예를 들어 API 응답이:
[
{
"id": 1,
"name": "김철수"
},
{
"id": 2,
"name": "이영희"
}
]
일 때 이름만 필요한 경우:
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
select: (data) =>
data.map(user => user.name),
});
결과:
[
"김철수",
"이영희"
]
컴포넌트에서 별도로 데이터를 가공하는 로직을 줄일 수 있다.
staleTime
staleTime은 데이터를 얼마 동안 최신 데이터(Fresh)로 판단할지 설정하는 옵션이다.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 1000 * 60 * 5,
});
5분 동안은 최신 데이터로 판단하기 때문에 불필요한 요청을 줄일 수 있다.
gcTime
gcTime은 사용하지 않는 캐시 데이터를 메모리에 얼마나 유지할지 결정하는 옵션이다.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
gcTime: 1000 * 60 * 5,
});
'Library' 카테고리의 다른 글
| Zustand (0) | 2026.06.22 |
|---|---|
| React Hook Form (0) | 2026.06.20 |
| TypeScript를 쓰는데 왜 Zod까지 사용할까? (0) | 2026.06.18 |