手動フェッチの混乱を超えて
2018年当時、私はすべてのAPIコールに対して, 同じようなロジックを何週間もかけて書いていました。フェッチのたびに、data、loading、errorという3つの状態を初期化していたのです。コンポーネントはuseEffectフックで膨れ上がり、デバッグは悪夢のようでした。useStateとuseContextだけでコンポーネント間のデータ同期を試みたことがある人なら、アプリが成長した瞬間にそのアーキテクチャが崩壊することを知っているはずです。
私たちが陥りやすいミスは、サーバー状態(Server State)をクライアント状態(Client State)と同じように扱ってしまうことです. クライアント状態はローカルなものであり、サイドバーの開閉やテキスト入力のように完全に制御可能です。一方、サーバー状態はリモートにあります。それは自分が所有しているものではなく、数ミリ秒で古くなる可能性のある「スナップショット」に過ぎません。Reduxのような従来のツールでは、このライフサイクルを手動で管理せざるを得ず、キャッシュ、無効化、再取得のためのコードを何度も書くことになります。
TanStack Query(旧React Query)は、非同期状態マネージャーとして機能します。キャッシュを処理し、読み込み状態を自動的に管理します。これにより、何百行ものボイラープレートを書くことなく、UIとバックエンドの同期を保つことができます。APIコールを、煩雑なサイドエフェクトとしてではなく、宣言的な依存関係として扱うことができるようになります。
セットアップ:ライブラリの準備
まず、プロジェクトにライブラリを導入しましょう。TanStack Queryはフレームワークに依存しませんが、ここではReactパッケージに焦点を当てます。また、DevToolsのインストールを強くお勧めします。キャッシュに何が保持されているかを瞬時に確認できるため、開発において非常に重宝します。
# npmを使用する場合
npm install @tanstack/react-query @tanstack/react-query-devtools
# yarnを使用する場合
yarn add @tanstack/react-query @tanstack/react-query-devtools
# pnpmを使用する場合
pnpm add @tanstack/react-query @tanstack/react-query-devtools
インストールしたら、アプリケーションをQueryClientProviderでラップします。これにより、ツリー内のすべてのコンポーネントがクエリキャッシュと通信できるようになります。
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* アプリのコンポーネント */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
実装:よりクリーンで高速なデータ処理
セットアップが完了したら、useQueryフックを使用できます。ここからが本番です。最近のプロジェクトでは、20以上のAPIエンドポイントがありましたが、このパターンに切り替えたことで、約1,200行の冗長な状態ロジックを削除することができました。
useQueryによるデータ取得
プロジェクトリストの実装例を見てみましょう。データ取得のためにuseEffectを排除することで、コンポーネントがいかにクリーンに保たれているかに注目してください。
import { useQuery } from '@tanstack/react-query';
const fetchProjects = async () => {
const response = await fetch('/api/projects');
if (!response.ok) throw new Error('ネットワークレスポンスが正常ではありませんでした');
return response.json();
};
function ProjectList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
staleTime: 1000 * 60 * 5, // 5分間
});
if (isLoading) return <div>プロジェクトを読み込み中...</div>;
if (isError) return <div>エラー: {error.message}</div>;
return (
<ul>
{data.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}
よくある間違い:staleTimeとgcTime
最もよく見かけるミスは、staleTimeを無視することです。デフォルトでは0に設定されています。つまり、ユーザーがタブを切り替えて戻ってくるたびに、バックグラウンドでのフェッチがトリガーされます。最新のデータがあるのは良いことですが、不必要にサーバーに負荷をかける可能性があります。
- staleTime: データが「鮮度(fresh)」を保つ期間。この期間内であれば、ネットワークリクエストを行わずにキャッシュからデータが返されます。
- gcTime (旧 cacheTime): 非アクティブなデータがガベージコレクションされるまでメモリに残る期間。
月間ユーザー数5万人の本番アプリでは、グローバルなstaleTimeを60秒に設定しました。この単純な変更だけで、ユーザー体験を損なうことなくサーバー負荷を45%削減できました。
ミューテーションと無効化のハンドリング
データの取得は仕事の半分に過ぎません。データを送り返す必要もあります。useMutationフックはこれを完璧に処理します。コツはクエリの無効化(Query Invalidation)です。プロジェクトを追加した後、リストを自動的に更新させたい場合に有効です。
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddProjectForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newProject) => {
return fetch('/api/projects', {
method: 'POST',
body: JSON.stringify(newProject),
});
},
onSuccess: () => {
// 'projects'を即座に古い(stale)とマークし、再取得をトリガーします
queryClient.invalidateQueries({ queryKey: ['projects'] });
},
});
const handleSubmit = (event) => {
event.preventDefault();
mutation.mutate({ name: '素晴らしい新プロジェクト' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? '保存中...' : 'プロジェクトを追加'}
</button>
</form>
);
}
プロのチェックポイント:監視と信頼性
クエリの実装が終わったら、DevToolsを使用して挙動を確認しましょう。キャッシュが裏側で予期しない動きをしていないか、100%確信を持つための唯一の方法です。
以下の4つのチェックポイントを意識してください:
- 階層的なキー: 単純な文字列ではなく
['projects', id]を使用します。これにより、キャッシュの他の部分に影響を与えず、特定のデータのみを無効化の対象にできます。 - リトライロジック: TanStack Queryは、デフォルトで失敗したリクエストを3回リトライします。重要なUI要素では、ユーザーが10秒間もローディング画面を見続けなくて済むよう、リトライ回数を1回に減らすことも検討してください。
- ウィンドウのフォーカス: ブラウザのタブを切り替える際にDevToolsを観察してください。データが静的なものであれば、
refetchOnWindowFocusを無効にして帯域幅を節約しましょう。 - Error Boundaries:
throwOnErrorオプションを使用して、コンポーネントツリーの上位でAPIエラーをキャッチします。これにより、個々のコンポーネントをよりシンプルに保てます。
サーバー状態の管理は、頭の痛い問題であるべきではありません。重労働をTanStack Queryに任せることで、レースコンディションとの戦いをやめ、機能の開発に集中できるようになります。このアーキテクチャへの転換は、より堅牢なアプリと、より幸福な開発チームを生み出すでしょう。

