先に要点
- TanStack Query(旧 React Query)は、「サーバから取ったデータ(server state)」 を扱う専用ライブラリ。「useState + useEffect + fetch + ローディング + エラー + 再取得」 を毎回書く苦痛を 「 useQuery」 1行 で解消する。
- 核は 「 stale-while-revalidate」 のキャッシュ戦略。「古いキャッシュをまず表示 → バックグラウンドで新しいデータを取り直す → 変わっていれば更新」 という挙動を、デフォルトで提供する。
- 提供機能は広い: キャッシュ管理 / 再試行 / 自動再取得(focus / online)/ ページネーション / 無限スクロール / 楽観的更新 / プリフェッチ / DevTools。「データ取得まわりの定番ハマりどころ」 が一通り入っている。
- React 専用ではない。「 Solid / Vue / Svelte / Angular」 でも同じ概念で使える。tRPC や Hono RPC の標準クライアントとしても採用されている。
React Query ってよく聞くけど、Redux とどう違うの? 「useEffect で fetch するのと何が違うの?」 「今は TanStack Query って名前変わった?」 ── 2018年頃から急速に広まった React Query は、2022年に TanStack Query へ改名し、現在はマルチフレームワーク対応の 「サーバ状態ライブラリ」 として定着しました。
ざっくり言うと、TanStack Query は サーバから取ったデータをキャッシュして、必要なときに同期する 専門ライブラリです。 「サーバ状態(server state)」 を扱うことに特化していて、「クライアント側だけの状態(UI 状態など)」 とは別物として扱う、というのが設計思想の核心です。
この記事では、2026年5月時点の TanStack Query v5 系をベースに、仕組み・基本の書き方・Redux 等との違い・採用判断軸 を整理します。 仕様は活発に変化しているので、最終確認は 公式ドキュメント を見るのが安全です。
なぜ TanStack Query が必要になったか
「なぜわざわざ専用ライブラリが必要なのか」 を 「useState + useEffect」 と比較すると分かりやすくなります。
// 「素朴な」 書き方
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return <p>{user.name}</p>;
}
これに対して、TanStack Query では:
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return <p>{user.name}</p>;
}
行数の差以上の違い
素朴な書き方では 別画面に行って戻ると毎回再取得」 「タブを切り替えるたびに再フェッチ」 「エラーで自動再試行する」 などが手動 / 未対応。TanStack Query はこれらをデフォルトで適切に処理する。
再取得のタイミング
「 ウィンドウフォーカス時」 「ネットワーク再接続時」 「mount 時」 など、「いつ取り直すか」 を細かく設定できる。「画面を放置していたら古いデータが表示されたまま」 が起きづらい。
エラー / リトライ
「 失敗したら指数バックオフで自動リトライ」 が標準。「ネットワークの一瞬の不調」 で諦めない。
「非同期の細かい挙動を毎回手書きしなくていい」 のが、TanStack Query の最大の利点です。
キャッシュの考え方 — stale と fresh
TanStack Query のキャッシュは、stale-while-revalidate という考え方に基づいています。
「fresh」 状態
取ったデータが 「まだ新しい」 とみなされる期間。「staleTime」 で指定。デフォルトは 0 ms(取った瞬間に古くなる扱い)。
「stale」 状態
「 まだキャッシュには残っているが、新しい値を取りに行ってもいい」 状態。表示は即時、裏側でリフェッチ。
「cacheTime」 / 「gcTime」
「 どのコンポーネントからも使われなくなった後、何分間キャッシュを保持するか」。デフォルトは 5分。
refetch のトリガ
「stale なときに mount / window focus / network reconnect」 で自動再取得。「そんなに頻繁にいらない」 場合は 「staleTime」 を伸ばす。
`データを 「絶対今の値」 か 「だいたい近い値」 のどちらで扱うか」 を 「staleTime」 でチームごとに調整するのが、TanStack Query の運用の中心です。
主要機能の一覧
「データ取得まわりの定番タスク」 が一通りそろっています。
| 機能 | API | 用途 |
|---|---|---|
| 取得 | 「useQuery」 | GET 系。データを読む。 |
| 更新 | 「useMutation」 | POST / PUT / DELETE 系。書き込み。 |
| 無効化 | 「queryClient.invalidateQueries」 | 「 この queryKey のキャッシュを古い扱いにする」。 |
| 楽観的更新 | 「onMutate / setQueryData / onError」 | サーバ応答前に UI を仮更新。 |
| 無限スクロール | 「useInfiniteQuery」 | ページネーション + 「次へ」 を自動管理。 |
| プリフェッチ | 「queryClient.prefetchQuery」 | 「 次の画面に行く前に先取り」。 |
| サスペンス連携 | 「useSuspenseQuery」 | React Suspense と組み合わせる。 |
| DevTools | 「@tanstack/react-query-devtools」 | キャッシュ状態を視覚化。 |
「データ取得で手で書きたくない処理は、まず TanStack Query にあるか確認する」 だけで、ほぼ何でも見つかる印象です。
Redux / Zustand / Context との違い
「なんで状態管理ライブラリの代わりに使うの?」 という疑問は、よく出ます。
| 軸 | Redux / Zustand / Context | TanStack Query |
|---|---|---|
| 得意領域 | クライアント状態(UI 状態、フォーム、モーダル) | サーバ状態(API から取ったデータ) |
| キャッシュ | 自前で実装 | 標準で提供 |
| 非同期 | middleware や thunk で手書き | 標準で提供 |
| 再取得 / 同期 | 手書き | 標準で提供 |
| 主な責務 | 「 クライアントが持つべき状態」 | 「 サーバが真実とする状態のキャッシュ」 |
| 組み合わせ | TanStack Query と併用がベスト | Redux / Zustand と併用がベスト |
要点は Redux 系と TanStack Query は競合ではなく、役割分担で併用する ことです。 「サーバから取ったデータは TanStack Query、UI 状態(ダークモード / モーダル開閉)は Zustand / Context」 という棲み分けが2026 年現在の主流です。
楽観的更新の書き方
UX を高める典型機能 「楽観的更新」 の書き方を見ます。
const queryClient = useQueryClient();
const toggleLike = useMutation({
mutationFn: (postId) =>
fetch(`/api/posts/${postId}/like`, { method: 'POST' }),
onMutate: async (postId) => {
// 進行中の取得をキャンセル
await queryClient.cancelQueries({ queryKey: ['posts'] });
// 前の値を保持(ロールバック用)
const previous = queryClient.getQueryData(['posts']);
// 楽観的に更新
queryClient.setQueryData(['posts'], (old) =>
old.map(p => p.id === postId ? { ...p, liked: !p.liked } : p)
);
return { previous };
},
onError: (err, postId, context) => {
// 失敗したら前の値に戻す
queryClient.setQueryData(['posts'], context.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
楽観的更新とは
「 サーバ応答を待たずに、UI を先に更新する」 こと。ボタンを押したら即色が変わる、いいねが即反映する、といった 「早く感じる UX」 を実現する。
失敗時に 「前の値に戻す」 ロジックが必須。TanStack Query の 「onMutate → onError」 パターンで自然に書ける。
最終的な同期
「onSettled」 で 関連キャッシュを invalidate して、最終的にはサーバの値で上書きされるようにする。
React 19 との関係
React 19 の 「useOptimistic」 は Server Actions と組み合わせて使う仕組み。「TanStack Query で全部完結する」 ケースと 「React 標準の useOptimistic を使う」 ケースが棲み分かれつつある。
`非同期処理の 「応答までの空白」 をなくす」 のが楽観的更新の核心で、TanStack Query はこれを正攻法で書きやすくしてくれます。
いつ TanStack Query を使うか
採用判断の目安を整理します。
向いている
① React / Solid / Vue / Svelte の中〜大規模アプリ、② 複数画面で同じデータを表示する SPA、③ 非同期取得が多い管理画面 / SaaS、④ tRPC / GraphQL Code Generator の組み合わせ。
慎重に
① 純粋に App Router + RSC + Server Actions だけで完結する小規模アプリ(「useQuery が要らない」 ケースが増える)、② 「1ページだけ」 のような極小プロジェクト、③ サーバ側で全部レンダリング前提のサイト。
RSC / Server Actions との関係
「 データ取得の主役を RSC に寄せる」 場合、TanStack Query の必要性は下がる。ただし、「クライアント側のリアルタイム更新」 「フォーカス時の再取得」 が必要な場面では、依然として有力。
tRPC との相性
tRPC の React 統合は TanStack Query をベースに作られている。「tRPC を使う = TanStack Query もセットで使う」 と言って差し支えない。
「React 系のフルスタックアプリ」 を作るなら、いまも 「必須スキル」 に近い位置にいます。
ハマりやすいポイント
便利な反面、現場で踏みやすい注意点も整理します。
①「queryKey」 設計
「 queryKey が同じだとキャッシュを共有する」 ので、「配列の構造」 を最初に決めておく。「 ['posts', { userId, page }]」 のような構造 がチーム標準的。
② staleTime デフォルト 0
「 取った瞬間 stale 扱い」 になり、毎回 refetch がトリガーされやすい。「staleTime: 1000 * 60」 などを 「defaultOptions」 で設定すると、無駄な再取得が減る。
③ 無効化漏れ
「 更新したのにリストが古いまま」 が起きやすい。「useMutation の onSuccess で invalidateQueries」 を必ず入れる。
④ DevTools を入れていない
「 どのキャッシュが今 stale で、どれが fresh か」 を視覚化できる DevTools が必須レベルに便利。開発時は必ず入れる。
「設定の妥当性を体感で詰める」 のが、TanStack Query の運用を綺麗にする最大のコツです。
AI 時代の TanStack Query
AI 連携の文脈でも TanStack Query は活躍します。
AI 応答のストリーミング
「 useQuery で AI レスポンスを取りつつ、ストリーミングは別途処理」 のような構成が組める。「生成中の状態 + 過去履歴の表示」 を統一的に扱える。
プリフェッチで体感速度向上
「 次に必要そうな AI 応答」 をプリフェッチしておくことで、「クリック後の待ち時間」 を実質ゼロにできる。AI のレスポンス遅さを UX 上隠せる。
楽観的更新 × AI
「 AI チャットで送信 → 即ユーザーメッセージを表示 → AI 応答が返ったら追加」 という典型的な UX を、「useMutation + 楽観的更新」 で素直に書ける。
tRPC + TanStack Query + AI
「 AI 呼び出しを tRPC mutation として定義 → TanStack Query 統合で再取得 / 楽観的更新」 という構成は、AI 機能を持つアプリの定番パターン。
「AI 応答の不確実な遅延」 を 「TanStack Query のキャッシュとプリフェッチ」 で吸収する、というのは AI 時代の UX 設計で頻出するパターンです。
TanStack Query に関するよくある質問
Q. React Query と TanStack Query は同じものですか?
A. 同じです。2022 年に 「React Query」 から 「TanStack Query」 に改名し、React 以外のフレームワーク(Solid / Vue / Svelte / Angular)もサポートする多目的ライブラリになりました。「React Query」 は React 向けアダプタの呼称として残っています。
Q. SWR と TanStack Query、どちらを選ぶべきですか?
A. 用途と好みで分けます。SWR は Vercel 製の軽量・シンプル路線、TanStack Query は機能が豊富で 「何でもできる」 路線。「小さなアプリで軽さ重視」 なら SWR、「本格的な機能と DevTools が欲しい」 なら TanStack Query、というのがざっくりの選び方です。
Q. RSC と Server Actions だけで完結する場合、TanStack Query は不要ですか?
A. ケースバイケースです。完全に 「読みは RSC、書きは Server Actions、再取得は revalidatePath」 で済む場合は不要です。ただし、「Realtime な再取得」 「フォーカス再取得」 「楽観的更新」 が必要な場面では、TanStack Query を併用する設計が依然として有効です。
Q. tRPC と TanStack Query は必ずセットですか?
A. React で tRPC を使う場合は事実上セットです。「tRPC の React Hooks」 は内部的に TanStack Query を使っており、「useQuery / useMutation」 と同じ感覚で 「trpc.x.useQuery()」 が使えます。
Q. キャッシュ無効化を全部書くのが面倒です。
A. queryKey の階層を意識して、まとめて invalidate するのが定石です。「queryClient.invalidateQueries({ queryKey: ['posts'] })」 だけで 「['posts', ...]」 系すべてのクエリを無効化できます。
Q. SSR ではどう使いますか?
A. サーバ側でデータをフェッチ → dehydrate してクライアントに渡す → hydrateのパターンが基本です。Next.js / Remix での専用ヘルパー(「HydrationBoundary」 等)があります。
Q. TanStack Query を学ぶ最短ルートは?
A. ① 「useQuery」 で1つ取得、② 「useMutation」 で1つ更新、③ 「queryClient.invalidateQueries」 で再取得、④ DevTools をインストールして挙動を観察、の4段階が王道です。「動かしながらキャッシュ状態を見る」 のが、TanStack Query の理解を加速する最大のコツです。