PromleeBlog
sitemap
aboutMe

posting thumbnail
리액트 쿼리를 커스텀 훅으로 효율적으로 사용하는 방법
Efficient Pattern of Using React Query with Custom Hooks

📅

🚀

들어가기 전에 🔗

React, React Native 모두 적용 가능한 패턴입니다.
React Query를 사용해 데이터를 패칭하는 기능은 강력하지만, 여러 페이지나 컴포넌트에서 같은 쿼리를 반복해서 작성하면 코드가 길어지고 유지 관리가 어려워집니다. 이럴 때 커스텀 훅으로 쿼리 로직을 분리해두면, 재사용성과 가독성을 동시에 높일 수 있습니다.
이번 글에서는
React Query를 커스텀 훅으로 추상화
해서 효율적으로 사용하는 방법을 소개합니다. JSONPlaceholder API를 예로 들어 실전에서 바로 사용할 수 있는 형태로 설명드리겠습니다.

🚀

기본 구조 복습: useQuery 사용 예시 🔗

먼저 useQuery를 직접 사용하는 기본 구조를 한 번 짚고 넘어가겠습니다.
components/UserList.tsx
const fetchUsers = async () => {
  const res = await axios.get('https://jsonplaceholder.typicode.com/users');
  return res.data;
};
 
function UserList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });
 
  if (isLoading) return <div>로딩 중...</div>;
  if (error) return <div>에러 발생!</div>;
 
  return <div>{data.map((user) => user.name)}</div>;
}
위와 같은 코드는 한두 개 컴포넌트에서는 문제가 없지만, 여러 곳에서 동일하게 사용되면 중복되는 코드가 많아집니다.

🚀

커스텀 훅으로 추상화하기 🔗

이제 이 패턴을 커스텀 훅으로 만들어보겠습니다. 예를 들어 useUsers.ts라는 파일을 만들어 다음과 같이 구성할 수 있습니다:
hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
 
const fetchUsers = async () => {
  const res = await axios.get('https://jsonplaceholder.typicode.com/users');
  return res.data;
};
 
export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
  });
}
이제 컴포넌트에서는 다음과 같이 사용할 수 있습니다:
components/UserList.tsx
import { useUsers } from '../hooks/useUsers';
 
export function UserList() {
  const { data, isLoading, error } = useUsers();
 
  if (isLoading) return <div>로딩 중...</div>;
  if (error) return <div>에러 발생!</div>;
 
  return <div>{data.map((user) => user.name)}</div>;
}

🚀

파라미터를 받는 커스텀 훅 만들기 🔗

다음은 특정 사용자의 게시글을 가져오는 useUserPosts(userId) 형태의 훅입니다:
hooks/useUserPosts.ts
export function useUserPosts(userId: number) {
  return useQuery({
    queryKey: ['user', userId, 'posts'],
    queryFn: async () => {
      const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}/posts`);
      return res.data;
    },
    enabled: !!userId,
  });
}
enabled: !!userId 옵션은 userId가 정의되지 않은 경우에는 쿼리가 실행되지 않도록 방지해줍니다.

🚀

쿼리 옵션을 외부에서 전달받기 🔗

훅을 사용할 때 유연하게 옵션을 전달하고 싶다면 다음처럼 options를 인자로 받을 수 있습니다:
hooks/useUsers.ts
export function useUsers(options?: UseQueryOptions) {
  return useQuery({
    queryKey: ['users'],
    queryFn: fetchUsers,
    ...options,
  });
}
이렇게 하면 사용하는 쪽에서 staleTime, onSuccess, onError 등을 자유롭게 설정할 수 있어 매우 유연합니다.

type safety 보장하기 🔗

fetch 결과의 타입을 정의해서 타입 안전성을 보장할 수 있습니다.
hooks/useUserPosts.ts
interface FetchUserPostsResponse {
  id: number;
  title: string;
  body: string;
}
 
export function useUserPosts(userId: number, options?: UseQueryOptions<FetchUserPostsResponse, Error>) {
  return useQuery({
    queryKey: ['user', userId, 'posts'],
    queryFn: async () => {
      const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}/posts`);
      return res.data;
    },
    ...options,
  });
}

🚀

결론 🔗

React Query는 강력한 기능을 제공하지만, 커스텀 훅으로 추상화하지 않으면 중복 코드가 발생하기 쉽습니다. 반복되는 queryKey, queryFn 등을 훅으로 분리하면 여러 페이지에서 효율적으로 재사용할 수 있고, 테스트와 유지보수도 쉬워집니다.
커스텀 훅을 잘 설계하면 팀 전체의 코드 품질과 일관성을 높일 수 있습니다. 앞으로 React Query를 사용할 때는
훅 기반 구조
를 기본으로 설계해보시길 권장드립니다.

참고 🔗