UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【TanStack Query】v5でonSuccessとonErrorが消えた理由

TanStack Queryでv4にあったonSuccessとonError、onSettledのAPIがv5で削除された

これらのAPIはわかりやすくていいのになと思ったのと、これらの代わりにuseEffectを使うというのが少し解せなくて理由を調べたところ、同様の反応がたくさんあったみたいでブログにまとめられていた

tkdodo.eu

結論として、useQueryの結果による状態の変更はonSuccessの内部ではうまくいかない可能性があり、それを阻止するためらしい

うまくいかない点としては例えば以下が挙げられる

  • stateの更新をonSuccess内で行ってしまう場合、状態が安定しない
  • staleTimeが定義されている場合、同期されない

stateの更新をonSuccess内で行ってしまう場合、状態が安定しない

まず、stateの更新をonSuccess内で行ってしまう場合、アプリが必要以上に頻繁にレンダリングされるだけでなく、中間のレンダーサイクルに不正確な値が含まれる可能性があるとのこと

たとえば以下のコードでは、最初todosはundefinedなので長さは0、次にtodosは長さ5の配列になるが、中間のレンダーサイクルの影響でtodoCountは0のままとなり、最後にtodosもtodoCountも5になる挙動になる

export function useTodos() {
  const [todoCount, setTodoCount] = React.useState(0)
  const { data: todos } = useQuery({
    queryKey: ['todos', 'list'],
    queryFn: fetchTodos,
    //😭 please don't
    onSuccess: (data) => {
      setTodoCount(data.length)
    },
  })

  return { todos, todoCount }
}

このように、stateの更新をonSuccess内で行ってしまうと、戻り値のdataとstateの状態が異なったものになる時があり、これによりバグになる可能性が潜んでいる

staleTimeが定義されている場合、同期されない

staleTimeが定義されている場合、useQueryは常に最新のデータを取得するためにqueryFnを呼び出すわけではなくなり、その時間内はすべての読み取りがキャッシュから行われる

これは過剰な再フェッチを避けるための良い手段であるが、戻り値のdataの変更が常にonSuccessと同期している保証がない

onSuccess内で例えばReduxのdispatchなんかを実行しているとしたらdataは更新されているが、アプリ全体の状態が古いままといった不具合が発生する可能性がある

対応

上記2つのことから、戻り値のdataを監視するのが一番いい方法と考えられ、美しいコードではないがuseEffectを使うのがベターな選択肢になるという話だった

他参考

RFC: remove callbacks from useQuery · TanStack query · Discussion #5279 · GitHub

Tanstack's React Query Kicked `onSuccess`, `onError`, and `onSettled` Out of `useQuery`: Now What?! - DEV Community