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,
})
queryKey と queryFn はオブジェクトの必須プロパティです。
自動移行ツール(コードモッド)を活用する
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 で非推奨になっていた onSuccess・onError・onSettled コールバックが、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 は削除されていません。useMutationのonSuccess・onError・onSettledは 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 の設定オプション cacheTime が gcTime(ガベージコレクション時間)に改名されました。
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 型が厳格化
useInfiniteQuery の getNextPageParam と initialPageParam の扱いが変わりました。
v5 での変更点
v5 では initialPageParam が必須になり、getNextPageParam が undefined ではなく 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に移行 - [ ]
isLoading→isPendingに変更(特にenabled: falseを使っている箇所) - [ ]
cacheTime→gcTimeに変更 - [ ]
useInfiniteQueryにinitialPageParamを追加 - [ ]
getNextPageParamの終端値をundefined→nullに変更 - [ ]
useQueryの型引数の順序変更(useQuery<TData, TError>など)を確認
移行の進め方
コードベースが大きい場合は以下の順序で進めると効率的です。
- まず公式コードモッドで引数形式を一括変換
- TypeScript のエラーを全て表示して1つずつ対応
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 マイグレーションガイド にも詳細が載っているので、合わせて参照してください。
