説明が難しいシリーズ。
const query = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
initialData: { id: 0, title: 'xxx' }, // <- コレ
});
デバッグ用くらいにしか使ったことなくて気にしてなかったけど、場合によってはハマるなーと思ったので書いておく。
いつものパターン
まずはおさらい。
const query = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
});
この場合に、query.data.xxx
を取得するためには、query.isSuccess
であることを確認した上でアクセスするのが定石。
// この時点ではまだローディング中で、`undefined`の可能性がある
query.data?.title;
// 成功してたら安全
if (query.isSuccess) {
query.data.title;
}
これぞTanstack Queryって感じ。
initialData
があると
しかし。
const query = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
initialData: { id: 0, title: 'xxx' }, // <- コレ
});
こうなってると、なんと最初からundefined
は排除されてる。
// この時点でも、キャッシュがあるので`undefined`の可能性はない
query.data.title;
// いつも通りにも書ける
if (query.isSuccess) {
query.data.title;
}
ふむ。
better type narrowing for initialData · Issue #3310 · TanStack/query https://github.com/TanStack/query/issues/3310
何がハマるかというと
コードベース全体で見たときに、この指定があるクエリとないクエリという2種類が生まれることになる。
isSuccess
やdata
によって型ガードをやってるものと、そうでないものが生まれるってこと。
<Dummy p={aQuery.data.xxx} />
{bQuery.isSuccess && (
<Dummy p={bQuery.data.xxx} />
)}
個人的には型が変わると思ってなかったので、なんでこのクエリだけ型ガードなしで動いてんの?!TSの秘孔でもついた?ってなって混乱して時間を浪費した。
ちなみに、queryFn
単体だと、データを同期で返してもそうはならない。
const query = useQuery({
queryKey: ['todo', id],
queryFn: () => ({ id: 1, title: "Why?" })
});
query.data?.title;
placeholderData
の場合も、当然そうはならない。
const query = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
placeholderData: { id: 0, title: 'xxx' },
});
query.data?.title;
事前にキャッシュを食わせたいなら
ドキュメントにも書いてあるけど、これにはいくつかやり方がある。
Initial Query Data | TanStack Query Docs https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data
- インラインで
initialData
を渡す - 事前に
prefetchQuery()
しておく - 事前に
setQueryData()
を使う
後の2つでは、型は変わらない。
クエリのインラインでinitialData
を指定したときだけ、型が絞り込まれる。だからややこしい。
どういうケースで遭遇したか
あまり一般的なケースではないと思うけど、
- API経由ではなく、SSR時にページに直接埋め込まれるデータがあった
- そのデータを、他のAPI経由で取得するデータと同じく、Tanstack Queryを介して画一的に利用したかった
- そこで、インラインの
initialData
が指定されたカスタムクエリを使いまわしてた- (そもそもカスタムクエリ自体、Tanstack Query界隈では非推奨だった気もするけど)
- クエリではなくオプションを共有する: https://tanstack.com/query/latest/docs/framework/react/guides/query-options
- https://github.com/TanStack/query/discussions/3227
という場面で、なんでこのクエリだけ型エラーにならんの?なんで??ってなってた。
そもそも暗黙的にインラインオプション指定したクエリを、それが見えないままグローバルに共有するなって話かもしれない。
画一的にしたいのなら、やはり非同期側に揃っててほしい(多少の型ガード記述は冗長になるけど)な〜って思った日だった。 後からAPI化したときも直す必要ないし。