React Query 완벽 가이드 (with TypeScript, 낙관적 업데이트, 무한 스크롤)

서버 상태 관리, 이거 하나로 끝낸다.
실무에서 바로 쓰는 예제 & 개념 총정리


✅ 목차

  1. React Query란?
  2. 설치 및 세팅 (TypeScript 기준)
  3. 기본 사용법
  4. 주요 옵션 정리
  5. Mutation + 낙관적 업데이트
  6. 무한 스크롤 구현
  7. Devtools로 디버깅
  8. 실무 팁 & 마무리

1. React Query란?

TanStack Query (구 React Query)는 API 요청, 캐싱, 리페칭, 로딩/에러 상태 관리 등을 자동화해주는
서버 상태 관리 라이브러리입니다.

useState, useEffect로 비동기 처리하지 마세요.
React Query가 훨씬 깔끔하게 해줍니다!


2. 설치 및 세팅 (TypeScript 기준)

npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
// App.tsx or main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

<QueryClientProvider client={queryClient}>
  <App />
</QueryClientProvider>

3. 기본 사용법 (with TypeScript)

타입 선언

type User = {
  id: number;
  name: string;
};

API 함수

const fetchUsers = async (): Promise<User[]> => {
  const res = await fetch('/api/users');
  if (!res.ok) throw new Error('Network error');
  return res.json();
};

useQuery 사용

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

const Users = () => {
  const { data, isLoading, isError, error } = useQuery<User[]>({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });

  if (isLoading) return <p>Loading...</p>;
  if (isError) return <p>Error: {(error as Error).message}</p>;

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

4. 주요 옵션 정리

옵션설명
queryKey캐싱 키
queryFnAPI fetch 함수
enabled조건부 쿼리 실행
staleTime데이터 신선도 유효 시간
refetchOnWindowFocus창 포커스 시 재요청 여부
select응답 데이터 가공 (ex. 날짜 포맷)

5. Mutation + 낙관적 업데이트

1) 기본 Mutation 사용

import { useMutation } from '@tanstack/react-query';

const createUser = async (user: User) => {
  const res = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(user),
    headers: { 'Content-Type': 'application/json' },
  });
  return res.json();
};
const mutation = useMutation({
  mutationFn: createUser,
  onSuccess: () => {
    queryClient.invalidateQueries(['users']);
  },
});

2) 낙관적 업데이트

const mutation = useMutation({
  mutationFn: createUser,
  onMutate: async (newUser: User) => {
    await queryClient.cancelQueries(['users']);
    const prevUsers = queryClient.getQueryData<User[]>(['users']);

    queryClient.setQueryData(['users'], (old = []) => [...old, newUser]);

    return { prevUsers };
  },
  onError: (_err, _newUser, context) => {
    queryClient.setQueryData(['users'], context?.prevUsers);
  },
  onSettled: () => {
    queryClient.invalidateQueries(['users']);
  },
});
<button onClick={() => mutation.mutate({ id: Date.now(), name: 'Joseph' })}>
  Add User
</button>

6. 무한 스크롤 구현 (useInfiniteQuery)

1) API 함수

const fetchPosts = async ({ pageParam = 1 }): Promise<Post[]> => {
  const res = await fetch(`/api/posts?page=${pageParam}`);
  return res.json();
};

2) 무한 쿼리 설정

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

const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  getNextPageParam: (lastPage, allPages) => {
    return lastPage.length === 10 ? allPages.length + 1 : undefined;
  },
});

3) 렌더링

{data?.pages.map((page, i) => (
  <div key={i}>
    {page.map(post => <p key={post.id}>{post.title}</p>)}
  </div>
))}

{hasNextPage && (
  <button onClick={() => fetchNextPage()}>Load More</button>
)}

7. Devtools 디버깅

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

<ReactQueryDevtools initialIsOpen={false} />

쿼리 상태를 시각화해서 보는 디버깅 도구, 실무 필수!


8. 실무 팁 모음

  • queryKey는 배열로 구조화: ['user', userId]
  • 조건부 쿼리: enabled: !!id
  • select로 데이터 포맷 가공
  • 로컬 상태 따로 만들지 말고 쿼리 상태를 그대로 사용
  • 낙관적 업데이트는 UX 향상에 최고
  • useInfiniteQuery는 무한 스크롤 필수

✅ 마무리

React Query는 서버 데이터를 "자동으로, 안정적으로" 다룰 수 있게 도와줍니다.
TypeScript와 함께 쓰면 더욱 강력하고 안전한 코드 작성이 가능해요.

프론트엔드라면 꼭 알아야 할 필수 라이브러리입니다!


참고 자료

profile
프론트엔드 개발자

0개의 댓글