SOFTMOA TECHNOLOGY

React Query(TanStack Query)로 서버 상태 관리

소프트모아가 정리한 기술 기록입니다.

Contents
작성일 2025. 11. 30.

React Query(TanStack Query)로 서버 상태 관리



React Query는 서버 상태(서버에서 가져온 데이터)를 효율적으로 관리하는 라이브러리입니다. 캐싱, 동기화, 백그라운드 업데이트를 자동으로 처리합니다.



언제 사용하나요?



  • API에서 데이터를 가져와 캐싱할 때

  • 데이터 자동 갱신(재검증)이 필요할 때

  • 페이지네이션, 무한 스크롤 구현

  • 낙관적 업데이트(Optimistic Update)

  • 로딩/에러 상태 관리 간소화



설치


npm install @tanstack/react-query
npm install @tanstack/react-query-devtools


초기 설정


// App.jsx
import { QueryClient, QueryClientProvider } from \047@tanstack/react-query\047;
import { ReactQueryDevtools } from \047@tanstack/react-query-devtools\047;

const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1분간 fresh
gcTime: 1000 * 60 * 5, // 5분간 캐시 유지
retry: 1, // 실패시 1회 재시도
refetchOnWindowFocus: false,
},
},
});

function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
<ReactQueryDevtools />
</QueryClientProvider>
);
}


useQuery - 데이터 조회


import { useQuery } from \047@tanstack/react-query\047;

function UserList() {
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey: [\047users\047],
queryFn: async () => {
const response = await fetch(\047/api/users\047);
if (!response.ok) throw new Error(\047Network error\047);
return response.json();
},
});

if (isLoading) return <div>로딩 중...</div>;
if (isError) return <div>에러: {error.message}</div>;

return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}


queryKey - 캐시 키


// 배열 형태로 캐시 키 지정
useQuery({ queryKey: [\047users\047], ... }); // 전체 목록
useQuery({ queryKey: [\047users\047, userId], ... }); // 특정 유저
useQuery({ queryKey: [\047users\047, { status: \047active\047 }], ... }); // 필터


useMutation - 데이터 변경


import { useMutation, useQueryClient } from \047@tanstack/react-query\047;

function CreateUser() {
const queryClient = useQueryClient();

const mutation = useMutation({
mutationFn: async (newUser) => {
const response = await fetch(\047/api/users\047, {
method: \047POST\047,
body: JSON.stringify(newUser),
});
return response.json();
},
onSuccess: () => {
// 성공 시 users 쿼리 무효화 (재조회)
queryClient.invalidateQueries({ queryKey: [\047users\047] });
},
});

const handleSubmit = () => {
mutation.mutate({ name: \047홍길동\047 });
};

return (
<button
onClick={handleSubmit}
disabled={mutation.isPending}
>
{mutation.isPending ? \047저장 중...\047 : \047저장\047}
</button>
);
}


낙관적 업데이트 (Optimistic Update)


const mutation = useMutation({
mutationFn: updateUser,
onMutate: async (newData) => {
// 진행 중인 쿼리 취소
await queryClient.cancelQueries({ queryKey: [\047user\047, id] });

// 이전 데이터 백업
const previousUser = queryClient.getQueryData([\047user\047, id]);

// 낙관적으로 캐시 업데이트
queryClient.setQueryData([\047user\047, id], newData);

return { previousUser };
},
onError: (err, newData, context) => {
// 에러 시 롤백
queryClient.setQueryData([\047user\047, id], context.previousUser);
},
onSettled: () => {
// 완료 후 재조회로 동기화
queryClient.invalidateQueries({ queryKey: [\047user\047, id] });
},
});


페이지네이션


function UserList({ page }) {
const { data, isPlaceholderData } = useQuery({
queryKey: [\047users\047, page],
queryFn: () => fetchUsers(page),
placeholderData: keepPreviousData, // 이전 데이터 유지
});

return (
<div style={{ opacity: isPlaceholderData ? 0.5 : 1 }}>
{data?.users.map(user => ...)}
</div>
);
}


무한 스크롤


import { useInfiniteQuery } from \047@tanstack/react-query\047;

function InfiniteUsers() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: [\047users\047],
queryFn: ({ pageParam = 0 }) => fetchUsers(pageParam),
getNextPageParam: (lastPage) => lastPage.nextCursor,
});

return (
<>
{data?.pages.map(page =>
page.users.map(user => <UserCard user={user} />)
)}
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
더 보기
</button>
</>
);
}


주요 옵션









옵션설명
staleTime데이터가 fresh로 유지되는 시간
gcTime비활성 캐시 유지 시간
refetchOnWindowFocus창 포커스 시 재조회
retry실패 시 재시도 횟수
enabled쿼리 활성화 여부


vs Redux/Zustand


React Query는 서버 상태용, Redux/Zustand는 클라이언트 상태용으로 함께 사용하는 것이 좋습니다.

Share this

FrontEnd

Have a questions?

견적 및 기술문의

mobile : 010-7931-4813

Contact Form