M

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 は違います。

デフォルトで非常に積極的に再取得し、ユーザーが能動的に再取得をリクエストしなくてもそうします。 refetchOnMountrefetchOnWindowFocusrefetchOnReconnectなどはデータの正確さを保つのに素晴らしいものですが、自動的なバックグラウンド処理が失敗した時、分かりにくい 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 氏に特別な感謝を送ります。