React Query는 React 애플리케이션에서 서버 상태(Server State)를 관리하기 위한 라이브러리입니다.
클라이언트에서 서버의 데이터를 요청하고 관리하는 작업을 단순화하며,
fetching, caching, 동기화, 자동 갱신 등의 기능을 제공합니다.
React의 기본 상태관리 도구(예: useState, useEffect)는 로컬 상태(Client State) 관리에 적합
하지만 서버에서 가져오는 비동기 데이터를 관리할 때는 다음과 같은 문제가 발생:
React Query는 이러한 서버 상태 관리를 자동화 및 최적화해줌
자동 캐싱(Caching)
- 요청된 데이터를 캐시에 저장해 중복 요청 방지
- 동일한 쿼리에 대해 서버 재요청 최소화
상태 관리 자동화
- 로딩(isLoading), 에러(isError), 성공(isSuccess) 상태 자동 제공
- 로직 분리 및 컴포넌트 간결화
다양한 옵션 제공
- 자동 재시도
- 백그라운드에서 데이터 자동 갱신
- 브라우저 포커스 시 자동 refetch
- 의존성 기반 쿼리 호출 제어 (enabled, staleTime 등)
서버 상태 동기화
- 최신 데이터 유지 보장
- 실시간에 가까운 사용자 경험 제공 가능
import { useState, useEffect } from 'react';
function TodoList() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setIsLoading(true);
try {
const response = await fetch('/api/todos');
const result = await response.json();
setData(result);
setIsError(false);
setError(null);
} catch (err) {
setIsError(true);
setError(err);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchData();
}, []);
if (isLoading) return <div>로딩 중...</div>;
if (isError) return <div>에러: {error.message}</div>;
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
import { useQuery } from '@tanstack/react-query';
function TodoList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json())
});
if (isLoading) return <div>로딩 중...</div>;
if (isError) return <div>에러: {error.message}</div>;
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
import { useQuery } from '@tanstack/react-query';
function TodoList() {
const {
// 데이터와 상태
data, // 성공적으로 가져온 데이터
dataUpdatedAt, // 마지막 데이터 업데이트 시간
error, // 에러 객체
errorUpdatedAt, // 마지막 에러 발생 시간
failureCount, // 실패 횟수
failureReason, // 실패 이유
isError, // 에러 상태
isFetched, // 한번이라도 가져왔는지
isFetchedAfterMount, // 마운트 후 가져왔는지
isFetching, // 현재 가져오는 중인지
isLoading, // 첫 로딩 상태
isLoadingError, // 로딩 중 에러
isPaused, // 일시 중지 상태
isPlaceholderData, // 임시 데이터 사용 중인지
isPreviousData, // 이전 데이터 사용 중인지
isRefetchError, // 리페칭 중 에러
isRefetching, // 리페칭 중인지
isStale, // 데이터가 오래됐는지
isSuccess, // 성공 상태
status, // 상태 문자열
// 함수들
refetch, // 수동 리페칭
remove, // 쿼리 캐시에서 제거
fetchStatus // 현재 fetch 상태
} = useQuery({
queryKey: ['todos'], // 쿼리 키 (필수)
queryFn: fetchTodos, // 데이터 페칭 함수 (필수)
// 일반 옵션
cacheTime: 1000 * 60 * 5, // 캐시 유지 시간 (5분)
enabled: true, // 자동 실행 여부
networkMode: 'online', // 네트워크 모드
staleTime: 1000 * 60, // 데이터 신선도 유지 시간 (1분)
suspense: false, // React Suspense 사용 여부
// 리페칭 옵션
refetchInterval: false, // 주기적 리페칭 간격
refetchIntervalInBackground: false, // 백그라운드 리페칭
refetchOnMount: true, // 마운트 시 리페칭
refetchOnReconnect: true, // 재연결 시 리페칭
refetchOnWindowFocus: true, // 윈도우 포커스 시 리페칭
// 재시도 옵션
retry: 3, // 재시도 횟수
retryDelay: attemptIndex => // 재시도 간격
Math.min(1000 * 2 ** attemptIndex, 30000),
retryOnMount: true, // 마운트 시 재시도
// 콜백 함수들
onError: (error) => {
console.error('요청 실패:', error);
},
onSuccess: (data) => {
console.log('데이터 수신 성공:', data);
},
onSettled: (data, error) => {
console.log('요청 완료', { data, error });
},
// 데이터 변환
select: (data) => {
return data.map(item => ({
...item,
title: item.title.toUpperCase()
}));
},
// 초기 데이터
initialData: () => [], // 초기 데이터
placeholderData: [], // 임시 데이터
// 기타 옵션
keepPreviousData: true, // 이전 데이터 유지
structuralSharing: true // 구조적 공유 사용
});
if (isLoading) return <div>로딩 중...</div>;
if (isError) return <div>에러: {error.message}</div>;
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={() => refetch()}>새로고침</button>
</div>
);
}