React, React Native 모두 적용 가능한 패턴입니다. React Query를 사용해 데이터를 패칭하는 기능은 강력하지만, 여러 페이지나 컴포넌트에서 같은 쿼리를 반복해서 작성하면 코드가 길어지고 유지 관리가 어려워집니다. 이럴 때 커스텀 훅으로 쿼리 로직을 분리해두면, 재사용성과 가독성을 동시에 높일 수 있습니다. 이번 글에서는React Query를 커스텀 훅으로 추상화해서 효율적으로 사용하는 방법을 소개합니다. JSONPlaceholder API를 예로 들어 실전에서 바로 사용할 수 있는 형태로 설명드리겠습니다.
useQuery
를 직접 사용하는 기본 구조를 한 번 짚고 넘어가겠습니다.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
라는 파일을 만들어 다음과 같이 구성할 수 있습니다: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,
});
}
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)
형태의 훅입니다: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
를 인자로 받을 수 있습니다:export function useUsers(options?: UseQueryOptions) {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
...options,
});
}
staleTime
, onSuccess
, onError
등을 자유롭게 설정할 수 있어 매우 유연합니다.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,
});
}
queryKey
, queryFn
등을 훅으로 분리하면 여러 페이지에서 효율적으로 재사용할 수 있고, 테스트와 유지보수도 쉬워집니다.
커스텀 훅을 잘 설계하면 팀 전체의 코드 품질과 일관성을 높일 수 있습니다. 앞으로 React Query를 사용할 때는