React Queryにおけるステータスチェック
Jun 13, 2023
これは、Dominik Dorfmeister 氏のブログ記事であるStatus Checks in React Queryを日本語訳してみたものです。
誤訳などあればIssueや PR を頂けると幸いです。
React Query の利点の 1 つはクエリのステータスへのアクセスが容易であることです。クエリーが読み込み中であるのか、エラーが起きているのか、すぐにわかります。 このために、このライブラリーは内部のステートマシーンに由来する多くの真偽値を公開しています。型定義を見てみると、クエリーは以下のいずれかの状態になります。
success
: クエリーは成功してdata
があるerror
: クエリーは機能せず、error
がセットされるloading
: クエリーはデータを持たず、現在初回のloading
中であるidle
:enabled
ではないのでクエリーが 1 度も実行されていない
更新: React Query の v4 において、idle 状態は削除されました。loading
の状態は、ただ”データがまだない”ことを示します。
isFetching
フラグが内部のステートマシーンの 1 部ではないことに留意してください。これはリクエスト中であれば常に真となる付加的なフラグです。フェッチ中かつ成功、フェッチ中かつエラーにはなり得ますが、同時に読み込み中かつ成功とはなり得ません。ステートマシーンがこれを保証します。
更新: v4 において、isFetching
フラグは補助的なfetchStatus
から導き出されます。新しいフラグのisPaused
と同じように。より詳しくは#13: Offline React Queryをご覧ください。
標準的な例
無効なクエリーのためのエッジケースなのでidle
状態は大抵省かれます。そのためほとんどの例はこのようになります。
const todos = useTodos();
if (todos.isLoading) {
return 'Loading...';
}
if (todos.error) {
return 'An error has occurred: ' + todos.error.message;
}
return <div>{todos.data.map(renderTodo)}</div>;
ここでは、まず最初にエラーと読み込み中のチェックを行います。これはおそらく、適切なユースケースと不適切なユースケースがそれぞれあります、。データ取得の解決策の多く、特に手製のものは、再取得のメカニズムを持たないか、明示的なユーザ操作に限っての再取得になります。
でも、React Query は違います。
デフォルトで非常に積極的に再取得し、ユーザーが能動的に再取得をリクエストしなくてもそうします。
refetchOnMount
、refetchOnWindowFocus
、refetchOnReconnect
などはデータの正確さを保つのに素晴らしいものですが、自動的なバックグラウンド処理が失敗した時、分かりにくい UX になるかもしれません。
バックグラウンドのエラー
多くの場面で、バックグラウンドでの再取得が失敗したら、ひっそりと無視されるかもしれません。しかし、上述のコードではそうなりません。ここで 2 つの例を見てみましょう。
- ユーザーがページを開いて、初回のクエリーがうまく読み込まれます。しばらくの間その画面で操作を行い、メールをチェックするのにブラウザーのタブを切り替えます。数分後に戻ってきた時、React Query はバックグラウンドで再取得を行います。今回、再取得は失敗します。
- ユーザーがリストビューの画面にいて、任意のアイテムをクリックして詳細なビューへ掘り下げます。これはうまくいったので、リストビューへと戻ります。再び詳細なビューへ行くとキャッシュのデータを目にすることになります。これは素晴らしいですね、バックグラウンドでの再取得が失敗する場合を除いては。
どちらの場合でも、クエリーの状態はこのようになります。
{
"status": "error",
"error": { "message": "Something went wrong" },
"data": [{ ... }]
}
ご覧の通り、エラーと古いデータと両方が存在します。これが React Query の素晴らしいところです。stale-while-revalidate のキャッシュのメカニズムを採用していて、たとえデータが古くなっていてもデータが存在すれば常に提供することができます。
何を表示するかは私たち次第です。エラーを表示することが重要ですか?古いデータがある場合、それだけ表示すれば十分ですか?少しバックグラウンドのエラーを表示しつつ両方表示するべきですか?
この問いに明確な答えはありません。ユースケースに依存します。しかしながら上述の 2 つの例に関していえば、データがエラー画面に置き換えられてしまうのは幾分紛らわしい UX のように思えます。
これは React Query が失敗したクエリーをエクスポネンシャルバックオフによるデフォルトで 3 度リトライすることを考慮するとより関連性が高く、古いデータがエラー画面に置き換わるのに数秒かかるかもしれません。 バックグラウンドでの再取得を示すインジケーターがなければ、本当に理解しにくいものになるでしょう。
私が大抵データの存在を最初にチェックするのはこのためです。
const todos = useTodos();
if (todos.data) {
return <div>{todos.data.map(renderTodo)}</div>;
}
if (todos.error) {
return 'An error has occurred: ' + todos.error.message;
}
return 'Loading...';
もう 1 度言いますが、何が正しいかという明確な原則はなく、ユースケースに強く依存します。積極的な再取得がもたらす結果を誰もが把握するべきで、単純な todo の例に従うよりも適宜コードを構成する必要があります。
なぜこのようなステータスチェックのパターンがある状況下で有害となり得るのかを最初に強調してくれたNiek Bosch 氏に特別な感謝を送ります。