Open17

React SuspenseとErrorBoundaryどう使うか

shuhei aoyamashuhei aoyama

以下の環境でSuspenseとErrorBoundaryの使い方を模索する

  • React v18
  • MUI v5
  • @tanstack/react-query v4
  • orval v6
shuhei aoyamashuhei aoyama

シンプルにSuspenseモードをSuspenseで利用するといわゆるFetch-on-render が提供できる (これはSuspenseを使わないでも同じ)

const Component = () => {
  const  result = useData({suspense: true});
  return <div>{result.data?.foo}</div>
}

<Suspense fallback={<div>Loading</div>}>
  <Component/>
</Suspense>
shuhei aoyamashuhei aoyama

Suspense & fetchをネストさせた場合にウォーターフォール的になるのか。なるならfetch位置の妥当性は... (これはSuspense関係ないか)

shuhei aoyamashuhei aoyama

理屈から言えばpending (例外) 位置より後ろは評価されないからpending解除されないと下流は流れない気がする

shuhei aoyamashuhei aoyama

エラー時の処理はhooks側(onError)に寄せるか、コンポーネント(ErrorBoundary)に寄せるか。

shuhei aoyamashuhei aoyama

Suspenseはmutation目的ではないので、useQueryが基本となる。
useQueryではisErrorによるコンポーネントでの制御を期待しているように見える。(v4ではonErrorがdeprecated)

shuhei aoyamashuhei aoyama

あたりまえの話ではあるけどqueryとmutationを分けましょう、が前提になる。
fetch(リポジトリ)まわり & ライブラリのおかげで同じようなI/Fになってるけど、Suspenseやエラー制御という観点では完全に別物。
というわけで、まずはquery系についてだけ整理。

shuhei aoyamashuhei aoyama

要するにこれか・・・
ある程度ここをパターン化したいんだ。

あらゆるコンポーネントの周りにサスペンスバウンダリを置こうとしないようにしてください。ユーザに見せたいロードの各ステップよりもサスペンスバウンダリを細かく設置すべきではありません。デザイナと一緒に作業している場合は、ロード中表示をどこに配置するべきか尋ねてみてください。おそらく、それはデザインの枠組みにすでに含まれているでしょう。

すべてのコンポーネントを別々のエラーバウンダリでラップする必要はありません。エラーバウンダリの粒度について考える際は、エラーメッセージをどこに表示するのが理にかなっているかを考えてみてください。例えば、メッセージングアプリでは、会話のリストをエラーバウンダリで囲むのが理にかなっています。また、メッセージを個別に囲むことも理にかなっているでしょう。しかし、アバターを 1 つずつ囲むことには意味がありません。

shuhei aoyamashuhei aoyama

useQuery({suspense:true}) をSuspense対応hooksとすると、ある画面においてこのhooksを使ったときの表現したいローディング状態(表現) はまとまっているほうが良さそう。(XXX画面におけるローディング制御責務)
useSuspenseみたいな機能がない以上、この機能はコンポーネントとして提供するのがいいのかな。

shuhei aoyamashuhei aoyama

Result型もSuspense型も結局I/Fまわりは変わらない

shuhei aoyamashuhei aoyama

Result型


const SomeComponent = ({ data }) => {
  return <div>{data}</div>;
};

const result = useSomeQuery();

switch (result.status) {
  case 'loading':
    return <Loading />;
  case 'error':
    return <Error />;
  case 'success':
    return <SomeComponent data={result.data} />;
}

厳密にはこちらは Fetch-on-render じゃないのか。

shuhei aoyamashuhei aoyama

Suspense型

const SomeComponent = () => {
  const { data } = useSomeQuery({ suspense: true });
  return <div>{data}</div>;
};

<Suspense fallback={<Loading />}>
  <ErrorBoundary fallback={<Error />}>
    <SomeComponent />
  </ErrorBoundary>
</Suspense>;
shuhei aoyamashuhei aoyama

Suspense型はBoundaryとして状態をまとめられる。それはそう。
小さいローディングがあちこち発生するのを防げるというメリット。

shuhei aoyamashuhei aoyama

エラーをoptoinalにしつつ、useSomeQueryに沿った型が強制される感じがいいのかな。