← 記事一覧に戻る

TanStack Query v5(React Query v5)で型エラー・動かない時の対処法:v4からの破壊的変更5選

v4 から v5 にアップグレードしたら型エラーが大量に出た

TanStack Query(旧 React Query)の v5 は 2023年10月にリリースされましたが、v4 からの破壊的変更が多く、アップグレードでハマる開発者が後を絶ちません。

Property 'onSuccess' does not exist on type 'UseQueryOptions<...>'
Expected 1 arguments, but got 3.
Property 'cacheTime' does not exist on type 'QueryClientConfig'

こういったエラーが一気に出て途方に暮れた方も多いはずです。この記事では v5 の主要な破壊的変更を5つ解説し、それぞれの対処法を示します。

破壊的変更1:useQuery の引数形式がオブジェクト一択になった

v4 での書き方(v5 では型エラー)

// v4 では第1引数にキー、第2引数にfetchFn、第3引数にオプションを渡せた
const { data } = useQuery(
  ['user', userId],
  () => fetchUser(userId),
  { staleTime: 1000 * 60 }
)

v5 では 引数が1つのオプションオブジェクト形式のみ になりました。上記のコードは次のエラーになります。

Expected 1 arguments, but got 3.

v5 での正しい書き方

// v5:全てをオプションオブジェクトにまとめる
const { data } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  staleTime: 1000 * 60,
})

queryKeyqueryFn はオブジェクトの必須プロパティです。

自動移行ツール(コードモッド)を活用する

TanStack Query チームが公式のコードモッドを提供しています。大量のファイルを一括変換できます。

npx jscodeshift ./src \
  -t node_modules/@tanstack/react-query/build/codemods/v5/remove-overloads/remove-overloads.cjs \
  --extensions=ts,tsx

全ての変更を自動化できるわけではありませんが、引数形式の変換はほぼカバーされます。

破壊的変更2:onSuccess / onError / onSettled が useQuery から削除

これが v5 で最も影響の大きい変更です。v4 で非推奨になっていた onSuccessonErroronSettled コールバックが、v5 で完全に削除されました。

v4 での書き方(v5 では型エラー)

const { data } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  onSuccess: (data) => {
    // ← v5 では Property 'onSuccess' does not exist... エラー
    showSuccessToast(data.name)
  },
  onError: (error) => {
    // ← v5 では型エラー
    showErrorToast(error.message)
  },
})

v5 での対処法:useEffect で代替する

const { data, isError, error } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
})

useEffect(() => {
  if (data) {
    showSuccessToast(data.name)
  }
}, [data])

useEffect(() => {
  if (isError && error) {
    showErrorToast(error.message)
  }
}, [isError, error])

なぜ削除されたのか

TanStack Query チームの説明では、onSuccess には「同じクエリを複数のコンポーネントが使っていても、コールバックは1回しか呼ばれない」という問題がありました。これが予期しない動作の原因になりやすかったため、削除が決定されました。useEffect はコンポーネントごとに呼ばれるため、より直感的な挙動になります。

useMutation の onSuccess は削除されていませんuseMutationonSuccessonErroronSettled は v5 でも引き続き使えます。削除されたのは useQuery 側のみです。

破壊的変更3:isLoading の動作変更 → isPending を使う

enabled: false 時の動作が変わった

状態 v4 の isLoading v5 の isLoading v5 の isPending
初回フェッチ中(データなし) true true true
enabled: false かつデータなし true false true
キャッシュあり・再フェッチ中 false false false

v4 では enabled: false のクエリは isLoading: true を返していましたが、v5 では isLoading: false になりました。

この変更により、enabled: false のクエリをローディング判定に使っているコードが誤動作します。

対処法:isPending を使う

const { data, isPending, isFetching } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  enabled: !!userId,
})

// v5 では isPending を使う(enabled: false でも正しく true になる)
if (isPending) return <Spinner />

// isFetching は再フェッチ中も true(バックグラウンドロード表示に使う)
return (
  <div>
    {isFetching && <SmallSpinner />}
    <UserCard user={data} />
  </div>
)

isPending は「データがなくてフェッチ中、またはフェッチが無効化されている」状態を表します。v5 では isLoading より isPending を使うのが推奨です。

破壊的変更4:cacheTime が gcTime に改名

QueryClient や useQuery の設定オプション cacheTimegcTime(ガベージコレクション時間)に改名されました。

v4(v5 では型エラー)

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      cacheTime: 1000 * 60 * 5,  // ← v5: Property 'cacheTime' does not exist...
    },
  },
})

v5 での正しい書き方

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 5,   // ← gcTime に変更
      staleTime: 1000 * 60,
    },
  },
})

名前が変わっただけで動作は同じです。クエリが使われなくなってからキャッシュをメモリから削除するまでの時間を指定します。

破壊的変更5:useInfiniteQuery の pageParam 型が厳格化

useInfiniteQuerygetNextPageParaminitialPageParam の扱いが変わりました。

v5 での変更点

v5 では initialPageParam必須になり、getNextPageParamundefined ではなく null を返してページ終端を示す必要があります。

v4 での書き方(v5 ではエラー)

// v4(v5 では型エラー)
const { data, fetchNextPage } = useInfiniteQuery(
  ['posts'],
  ({ pageParam = 1 }) => fetchPosts(pageParam),
  {
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
  }
)

v5 での正しい書き方

// v5:initialPageParam が必須、終端は null で示す
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam }) => fetchPosts(pageParam),
  initialPageParam: 1,
  getNextPageParam: (lastPage) => lastPage.nextPage ?? null,
  //                                                    ^^^^ undefined ではなく null
})

initialPageParam を省略すると以下のエラーになります。

Property 'initialPageParam' is missing in type '{ queryKey: string[]; queryFn: ... }'

移行チェックリスト

v4 → v5 移行時に確認すべき項目をまとめます。

  • [ ] useQuery(['key'], fn, opts)useQuery({ queryKey, queryFn, ...opts }) に変更
  • [ ] onSuccess / onError / onSettled(useQuery)→ useEffect に移行
  • [ ] isLoadingisPending に変更(特に enabled: false を使っている箇所)
  • [ ] cacheTimegcTime に変更
  • [ ] useInfiniteQueryinitialPageParam を追加
  • [ ] getNextPageParam の終端値を undefinednull に変更
  • [ ] useQuery の型引数の順序変更(useQuery<TData, TError> など)を確認

移行の進め方

コードベースが大きい場合は以下の順序で進めると効率的です。

  1. まず公式コードモッドで引数形式を一括変換
  2. TypeScript のエラーを全て表示して1つずつ対応
  3. onSuccess 削除は useEffect への書き換えが必要なため、手動で対応
# パッケージのアップグレード
npm install @tanstack/react-query@^5

# TypeScript エラーの確認
npx tsc --noEmit

まとめ

| 変更内容 | v4 | v5 | |----------|----|----|| | useQuery の引数 | (key, fn, options) | ({ queryKey, queryFn, ...options }) | | onSuccess / onError | useQuery オプションで使用可 | 削除(useEffect で代替) | | ローディング判定 | isLoading | isPending を推奨 | | キャッシュ保持時間 | cacheTime | gcTime | | 無限スクロール | getNextPageParam: () => undefined で終端 | initialPageParam 必須、終端は null |

最も影響が大きいのは onSuccess の削除です。トースト通知・ログ・副作用処理が useQuery のコールバックに依存していた場合は、全て useEffect への移行が必要です。

公式の TanStack Query v5 マイグレーションガイド にも詳細が載っているので、合わせて参照してください。