ReactとReact Nativeのどちらにも適用できるパターンです。
React Queryのデータ取得機能は強力ですが、同じクエリを複数のページやコンポーネントに繰り返し書くと、コードが長くなり保守も難しくなります。
クエリの処理をカスタムフックへ分離すると、再利用性と読みやすさを同時に高められます。
今回は、
React Queryをカスタムフックへ抽象化
して効率的に使う方法を紹介します。
JSONPlaceholderを例に、実際の開発ですぐに使える形で説明します。
まず、useQueryを直接使う基本構造を確認します。
components/UserList.tsxconst 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.tsimport { 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.tsximport { 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.tsexport 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.tsexport function useUsers(options?: UseQueryOptions) {
return useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
...options,
});
}
利用する側でstaleTime、onSuccess、onErrorなどを自由に設定でき、柔軟性が高まります。
fetch結果の型を定義すると、型安全性を確保できます。
hooks/useUserPosts.tsinterface 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を使うときは、
フックを中心とした構造
を基本として設計してみてください。