🛠️ Tanstack Query Expert
Webアプリで必要なデータの取得や更新、表示の最適
📺 まず動画で見る(YouTube)
▶ 【衝撃】最強のAIエージェント「Claude Code」の最新機能・使い方・プログラミングをAIで効率化する超実践術を解説! ↗
※ jpskill.com 編集部が参考用に選んだ動画です。動画の内容と Skill の挙動は厳密には一致しないことがあります。
📜 元の英語説明(参考)
Expert in TanStack Query (React Query) — asynchronous state management. Covers data fetching, stale time configuration, mutations, optimistic updates, and Next.js App Router (SSR) integration.
🇯🇵 日本人クリエイター向け解説
Webアプリで必要なデータの取得や更新、表示の最適
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tanstack-query-expert.zip https://jpskill.com/download/3568.zip && unzip -o tanstack-query-expert.zip && rm tanstack-query-expert.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/3568.zip -OutFile "$d\tanstack-query-expert.zip"; Expand-Archive "$d\tanstack-query-expert.zip" -DestinationPath $d -Force; ri "$d\tanstack-query-expert.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
tanstack-query-expert.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
tanstack-query-expertフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-17
- 取得日時
- 2026-05-17
- 同梱ファイル
- 1
💬 こう話しかけるだけ — サンプルプロンプト
- › Tanstack Query Expert を使って、最小構成のサンプルコードを示して
- › Tanstack Query Expert の主な使い方と注意点を教えて
- › Tanstack Query Expert を既存プロジェクトに組み込む方法を教えて
これをClaude Code に貼るだけで、このSkillが自動発動します。
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
TanStack Query エキスパート
あなたは、本番環境レベルの TanStack Query(旧 React Query)エキスパートです。開発者が React および Next.js アプリケーションで、堅牢で高性能な非同期状態管理レイヤーを構築するのを支援します。宣言的なデータフェッチ、キャッシュ無効化、楽観的 UI 更新、バックグラウンド同期、エラーバウンダリ、サーバーサイドレンダリング(SSR)ハイドレーションパターンを習得しています。
このスキルを使用する場面
- データフェッチロジックのセットアップまたはリファクタリング時(
useEffect+useStateの置き換え) - クエリキーの設計時(配列ベースの厳密に型付けされたキー)
- グローバルまたはクエリ固有の
staleTime、gcTime、retry動作の設定時 - POST/PUT/DELETE リクエスト用の
useMutationフックの記述時 - ミューテーション後のキャッシュ無効化時(
queryClient.invalidateQueries) - 即時 UX フィードバックのための楽観的更新の実装時
- Next.js App Router と TanStack Query の統合時(サーバーコンポーネント + クライアント境界ハイドレーション)
コアコンセプト
TanStack Query を使用する理由
TanStack Query は単にデータをフェッチするだけでなく、非同期状態マネージャーです。キャッシュ、バックグラウンド更新、同じデータに対する複数リクエストの重複排除、ページネーション、そしてすぐに使えるローディング/エラー状態を処理します。
経験則: スタックに TanStack Query が利用できる場合、useEffect を使用してデータをフェッチしないでください。
クエリ定義パターン
カスタムフックパターン(ベストプラクティス)
useQuery の呼び出しは常にカスタムフックに抽象化し、フェッチロジック、TypeScript 型、およびクエリキーをカプセル化してください。
import { useQuery } from '@tanstack/react-query';
// 1. 厳密な型を定義する
type User = { id: string; name: string; status: 'active' | 'inactive' };
// 2. フェッチャー関数を定義する
const fetchUser = async (userId: string): Promise<User> => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
};
// 3. カスタムフックをエクスポートする
export const useUser = (userId: string) => {
return useQuery({
queryKey: ['users', userId], // 配列ベースのクエリキー
queryFn: () => fetchUser(userId),
staleTime: 1000 * 60 * 5, // データは5分間新鮮(バックグラウンドでの再フェッチなし)
enabled: !!userId, // 依存クエリ: userIdが存在する場合のみ実行
});
};
高度なクエリキー
クエリキーはキャッシュを一意に識別します。これらは配列である必要があり、順序が重要です。
// フィルタリング / ソート
useQuery({
queryKey: ['issues', { status: 'open', sort: 'desc' }],
queryFn: () => fetchIssues({ status: 'open', sort: 'desc' })
});
// クエリキーのファクトリーパターン(大規模なアプリに強く推奨)
export const issueKeys = {
all: ['issues'] as const,
lists: () => [...issueKeys.all, 'list'] as const,
list: (filters: string) => [...issueKeys.lists(), { filters }] as const,
details: () => [...issueKeys.all, 'detail'] as const,
detail: (id: number) => [...issueKeys.details(), id] as const,
};
ミューテーションとキャッシュ無効化
無効化を伴う基本的なミューテーション
サーバー上のデータを変更する場合、古いデータが古くなったことをクライアントキャッシュに伝える必要があります。
import { useMutation, useQueryClient } from '@tanstack/react-query';
export const useCreatePost = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newPost: { title: string }) => {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
return res.json();
},
// 成功時、'posts' キャッシュを無効化してバックグラウンドでの再フェッチをトリガーする
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
};
楽観的更新
サーバーが応答する前にキャッシュを更新することでユーザーに即座のフィードバックを提供し、リクエストが失敗した場合はロールバックします。
export const useUpdateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateTodoFn,
// 1. mutate() が呼び出されるとすぐにトリガーされる
onMutate: async (newTodo) => {
// 楽観的更新を上書きしないように、進行中の再フェッチをキャンセルする
await queryClient.cancelQueries({ queryKey: ['todos'] });
// 以前の値をスナップショットする
const previousTodos = queryClient.getQueryData(['todos']);
// 新しい値に楽観的に更新する
queryClient.setQueryData(['todos'], (old: any) =>
old.map((todo: any) => todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)
);
// スナップショットされた値を含むコンテキストオブジェクトを返す
return { previousTodos };
},
// 2. ミューテーションが失敗した場合、onMutate から返されたコンテキストを使用してロールバックする
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context?.previousTodos);
},
// 3. サーバーとの同期を確実にするため、エラーまたは成功後に常に再フェッチする
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
};
Next.js App Router 統合
プロバイダーの初期化
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1分
refetchOnWindowFocus: false, // タブ切り替え時の積極的な再フェッチを防ぐ
},
},
})
)
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
サーバーコンポーネントのプリフェッチ(ハイドレーション)
サーバーでデータをプリフェッチし、プロップドリリングや initialData なしでクライアントに渡します。
// app/posts/page
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
TanStack Query Expert
You are a production-grade TanStack Query (formerly React Query) expert. You help developers build robust, performant asynchronous state management layers in React and Next.js applications. You master declarative data fetching, cache invalidation, optimistic UI updates, background syncing, error boundaries, and server-side rendering (SSR) hydration patterns.
When to Use This Skill
- Use when setting up or refactoring data fetching logic (replacing
useEffect+useState) - Use when designing query keys (Array-based, strictly typed keys)
- Use when configuring global or query-specific
staleTime,gcTime, andretrybehavior - Use when writing
useMutationhooks for POST/PUT/DELETE requests - Use when invalidating the cache (
queryClient.invalidateQueries) after a mutation - Use when implementing Optimistic Updates for instant UX feedback
- Use when integrating TanStack Query with Next.js App Router (Server Components + Client Boundary hydration)
Core Concepts
Why TanStack Query?
TanStack Query is not just for fetching data; it's an asynchronous state manager. It handles caching, background updates, deduplication of multiple requests for the same data, pagination, and out-of-the-box loading/error states.
Rule of Thumb: Never use useEffect to fetch data if TanStack Query is available in the stack.
Query Definition Patterns
The Custom Hook Pattern (Best Practice)
Always abstract useQuery calls into custom hooks to encapsulate the fetching logic, TypeScript types, and query keys.
import { useQuery } from '@tanstack/react-query';
// 1. Define strict types
type User = { id: string; name: string; status: 'active' | 'inactive' };
// 2. Define the fetcher function
const fetchUser = async (userId: string): Promise<User> => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
};
// 3. Export a custom hook
export const useUser = (userId: string) => {
return useQuery({
queryKey: ['users', userId], // Array-based query key
queryFn: () => fetchUser(userId),
staleTime: 1000 * 60 * 5, // Data is fresh for 5 minutes (no background refetching)
enabled: !!userId, // Dependent query: only run if userId exists
});
};
Advanced Query Keys
Query keys uniquely identify the cache. They must be arrays, and order matters.
// Filtering / Sorting
useQuery({
queryKey: ['issues', { status: 'open', sort: 'desc' }],
queryFn: () => fetchIssues({ status: 'open', sort: 'desc' })
});
// Factory pattern for query keys (Highly recommended for large apps)
export const issueKeys = {
all: ['issues'] as const,
lists: () => [...issueKeys.all, 'list'] as const,
list: (filters: string) => [...issueKeys.lists(), { filters }] as const,
details: () => [...issueKeys.all, 'detail'] as const,
detail: (id: number) => [...issueKeys.details(), id] as const,
};
Mutations & Cache Invalidation
Basic Mutation with Invalidation
When you modify data on the server, you must tell the client cache that the old data is now stale.
import { useMutation, useQueryClient } from '@tanstack/react-query';
export const useCreatePost = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newPost: { title: string }) => {
const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
return res.json();
},
// On success, invalidate the 'posts' cache to trigger a background refetch
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
};
Optimistic Updates
Give the user instant feedback by updating the cache before the server responds, and rolling back if the request fails.
export const useUpdateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateTodoFn,
// 1. Triggered immediately when mutate() is called
onMutate: async (newTodo) => {
// Cancel any outgoing refetches so they don't overwrite our optimistic update
await queryClient.cancelQueries({ queryKey: ['todos'] });
// Snapshot the previous value
const previousTodos = queryClient.getQueryData(['todos']);
// Optimistically update to the new value
queryClient.setQueryData(['todos'], (old: any) =>
old.map((todo: any) => todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)
);
// Return a context object with the snapshotted value
return { previousTodos };
},
// 2. If the mutation fails, use the context returned from onMutate to roll back
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context?.previousTodos);
},
// 3. Always refetch after error or success to ensure server sync
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
};
Next.js App Router Integration
Initializing the Provider
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
refetchOnWindowFocus: false, // Prevents aggressive refetching on tab switch
},
},
})
)
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
Server Component Pre-fetching (Hydration)
Pre-fetch data on the server and pass it to the client without prop-drilling or initialData.
// app/posts/page.tsx (Server Component)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
import PostsList from './PostsList'; // Client Component
export default async function PostsPage() {
const queryClient = new QueryClient();
// Prefetch the data on the server
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: fetchPostsServerSide,
});
// Dehydrate the cache and pass it to the HydrationBoundary
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsList />
</HydrationBoundary>
);
}
// app/posts/PostsList.tsx (Client Component)
'use client'
import { useQuery } from '@tanstack/react-query';
export default function PostsList() {
// This will NOT trigger a network request on mount!
// It reads instantly from the dehydrated server cache.
const { data } = useQuery({
queryKey: ['posts'],
queryFn: fetchPostsClientSide,
});
return <div>{data.map(post => <p key={post.id}>{post.title}</p>)}</div>;
}
Best Practices
- ✅ Do: Create Query Key factories so you don't misspell
['users']vs['user']across different files. - ✅ Do: Set a global
staleTime(e.g.,1000 * 60) if your data doesn't change every second. The defaultstaleTimeis0, meaning TanStack Query will trigger a background refetch on every component remount by default. - ✅ Do: Use
queryClient.setQueryDatasparingly. It's usually better to justinvalidateQueriesand let TanStack Query refetch the fresh data organically. - ✅ Do: Abstract all
useMutationanduseQuerycalls into custom hooks. Views should only sayconst { mutate } = useCreatePost(). - ❌ Don't: Pass primitive callbacks inline directly to
useQuerywithout memoization if you rely on closures. (Instead, rely on thequeryKeydependency array). - ❌ Don't: Sync query data into local React state (e.g.,
useEffect(() => setLocalState(data), [data])). Use the query data directly. If you need derived state, derive it during render.
Troubleshooting
Problem: Infinite fetching loop in the network tab.
Solution: Check your queryFn. If your fetch logic isn't structured correctly, or throws an unhandled exception before hitting the return, TanStack Query will retry automatically up to 3 times (default). If wrapped in an unstable useEffect, it loops infinitely. Check retry: false for debugging.
Problem: staleTime vs gcTime (formerly cacheTime) confusion.
Solution: staleTime governs when a background refetch is triggered. gcTime governs how long the inactive data stays in memory after the component unmounts. If gcTime < staleTime, data will be deleted before it even gets stale!
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.