React Query는 TanStack에서 제공하는 서버 상태 관리를 위한 라이브러리이다.
여기서 서버 상태란 간단하게, 서버에서 가져온 데이터의 상태를 말한다.
(ex - 게시판의 글 목록, 검색 필터의 검색구분 목록(제목, 내용, 제목+내용 등), 날씨 데이터 등)
Rest API나 GraphQL을 사용할 때 유용하며, 서버 상태 관리를 쉽게 할 수 있다는 점 외에도 여러 기능을 제공하여 UX/DX 향상에 사용하기 좋다고 생각한다.
TanStack Query는 Vue, Angular, Svelte 등 다양한 프레임워크를 위한 라이브러리를 제공하며, 여기서 다루는 React Query는 @tanstack/react-query이다.
npm i @tanstack/react-query
npm i -D @tanstack/react-query-devtools # 서버에서 데이터를 가져온 시간을 확인할 수 있는 도구
useQuery 훅을 사용해 서버에서 데이터를 가져온다.
useQuery 훅의 매개변수는 queryKey, queryFn 등의 속성으로 이루어진 객체를 받는다.// 사용 예시
import { useQuery } from '@tanstack/react-query';
// fetch 함수를 컴포넌트 외부로 분리
const fetchUsers = async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
};
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers
});
if (isLoading) return <p>로딩 중...</p>;
if (error) return <p>에러 발생: {error.message}</p>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
useMutation 훅을 사용해 서버의 데이터를 변경한다.
useMutation 훅의 매개변수로 mutationFn, onSuccess, onError 등의 속성으로 이루어진 객체를 받는다.useQuery와 함께 사용할 때, 서버의 데이터를 변경함에 따라 useQuery로 가져온 데이터를 자동으로 최신화할 수 있다. (onSuccess 설정하여 별도의 refetch 필요없음)// 사용 예시
import { useMutation, useQueryClient } from '@tanstack/react-query';
const addUser = async ({ name }: { name: string }) => {
const res = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name }),
});
if (!res.ok) throw new Error('Failed to add user');
return res.json();
};
function AddUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: addUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
const handleAdd = () => {
mutation.mutate({ name: '새 유저' });
};
return (
<button onClick={handleAdd} disabled={mutation.isPending}>
{mutation.isPending ? '추가 중...' : '유저 추가'}
</button>
);
}
queryClient의 prefetchQuery 메서드를 사용해 서버의 데이터를 미리 가져온다.
useQuery 훅과 동일하게 매개변수 객체에 queryKey, queryFn을 포함한다.useQuery 훅과 함께 사용하여, useQuery를 실행할 때, prefetchQuery로 캐싱된 데이터가 있으면 즉시 사용하고, 없다면 서버에 요청을 보내는 방식으로 동작한다.// user 정보보기 페이지로 이동하는 링크 컴포넌트
import { useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
function UserPreviewLink({ userId }) {
const queryClient = useQueryClient();
// 마우스를 링크에 올리기만
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: () =>
fetch(`/api/users/${userId}`).then((res) => {
if (!res.ok) throw new Error('유저 정보를 불러오지 못했습니다.');
return res.json();
}),
});
};
return (
<Link
to={`/users/${userId}`}
onMouseEnter={handleMouseEnter}
onFocus={handleMouseEnter} // 키보드 tab 접근 시 prefetch
>
유저 프로필 보기
</Link>
);
}
// /users/:userId
import React from 'react';
import { useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
const fetchUser = async (userId) => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('유저 정보를 가져오지 못했습니다.');
return res.json();
};
export default function UserDetail() {
const { userId } = useParams();
const {
data: user,
isLoading,
isError,
error,
} = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 1000 * 60, // 1분 동안은 캐시 유지
});
if (isLoading) return <p>⏳ 유저 정보를 불러오는 중...</p>;
if (isError) return <p style={{ color: 'red' }}>❌ 오류: {error.message}</p>;
return (
<div style={{ padding: '1rem', border: '1px solid #ccc' }}>
<h2>👤 유저 상세 정보</h2>
<p><strong>ID:</strong> {user.id}</p>
<p><strong>이름:</strong> {user.name}</p>
<p><strong>이메일:</strong> {user.email}</p>
</div>
);
}
주의할 점
staleTime
useQuery훅을 사용할 때, staleTime을 설정하면 캐싱된 데이터를 사용하므로 불필요한 서버 요청을 줄여 UX를 향상시키고 서버 부담을 줄일 수 있다.
하지만, 서버의 데이터가 자주 변경되는 데이터의 staleTime을 길게 설정하면 서버의 데이터가 변경되었음에도 최신 데이터를 가져오지 않기 때문에 사용자에게 잘못된 정보를 제공하고 오류를 유발할 수 있다.
-> 상황에 따라 알맞은 staleTime을 설정하자.queryKey
useQuery훅을 사용할 때, 서로 다른 데이터임에도 queryKey 옵션을 동일하게 설정하면, 오류가 발생할 가능성이 크다.
-> queryKey 설정에 주의하자. queryKey 옵션은 배열로 설정하기 때문에, 아래와 같이 설정하는 방법도 좋다고 생각한다.const { data: user1 } = useQuery({ queryKey: ['user', '1'], // ... }); const { data: user2 } = useQuery({ queryKey: ['user', '2'], // ... });전역 상태 관리 용도로 사용하지말 것
react-query는 서버 상태 관리를 위한 도구이지, 전역 상태를 관리하기 위하 도구가 아니다.
전역 상태를 관리할 때는 zustand, Context API 같은 전역 상태 관리에 최적화된 도구를 사용하자.
더 많은 기능이 있지만, 포스팅에서 전부 다룰 수는 없기 때문에 여기서 마치겠습니다
TanStack Query
https://tanstack.com/query/latest
UX 향상에 관심이 많은 개발자로서, 이런 도구를 사용할 수 있다는 게 너무 행복했다...!
처음에는 staleTime을 무조건 길게 설정하는게 좋다고 생각하는 여러 실수들도 있었고, 사용법이 좀 헷갈렸지만 완벽히 적응한 나 칭찬해~ 😏
너무 남용하면 캐싱된 데이터가 많아져 오히려 앱 성능에 악영향을 미치진 않을까 걱정되기도 하지만 아직까지 눈에 띄는 문제는 없었으니 조심해서 사용하면 괜찮을 것 같다. 👍
그리고, Next.js같이 라우팅 기능이 포함된 프레임워크를 사용하지 않는다면 TanStack Router를 사용해서 prefetch같은 기능과 함께 사용하면 더 좋은 시너지가 있을거라 생각된다. 😁