jpskill.com
🛠️ 開発・MCP コミュニティ 🔴 エンジニア向け 👤 エンジニア・AI開発者

🛠️ Tanstack Query Expert

tanstack-query-expert

Webアプリで必要なデータの取得や更新、表示の最適

⏱ MCPサーバー実装 1日 → 2時間

📺 まず動画で見る(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本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
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
🪟 Windows (PowerShell)
$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. 1. 下の青いボタンを押して tanstack-query-expert.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → tanstack-query-expert フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

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 の置き換え)
  • クエリキーの設計時(配列ベースの厳密に型付けされたキー)
  • グローバルまたはクエリ固有の staleTimegcTimeretry 動作の設定時
  • 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, and retry behavior
  • Use when writing useMutation hooks 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 default staleTime is 0, meaning TanStack Query will trigger a background refetch on every component remount by default.
  • Do: Use queryClient.setQueryData sparingly. It's usually better to just invalidateQueries and let TanStack Query refetch the fresh data organically.
  • Do: Abstract all useMutation and useQuery calls into custom hooks. Views should only say const { mutate } = useCreatePost().
  • Don't: Pass primitive callbacks inline directly to useQuery without memoization if you rely on closures. (Instead, rely on the queryKey dependency 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.