🐳

Recoil を React Suspense と使う

2022/03/04に公開約2,500字

recoil はデフォルトで React.suspense をサポートします。
そのおかげで非同期なデータを非常にシンプルに扱うことができます。
今回は使用例を参考にどんな風に動作するかを確認します。

Recoil atom x React Suspense の使い方

使用例

testStateは非同期な値をもつatomです。
TestChildコンポーネントはtestStateを読み込んで使用と更新をしています。
TestコンポーネントはtestStateコンポーネントの親コンポーネントです。TestChildSuspenseで囲んで利用しています。

import { atom } from 'recoil';

const testState = atom<number>({
  key: 'test',
  default: new Promise((resolve) => {
    setTimeout(() => {
      resolve(100);
    }, 5 * 1000);
  }),
});

export { testState };
import * as React from 'react';
import { RecoilRoot, useRecoilState } from 'recoil';

import { testState } from '@src/stores/test';

function TestChild() {
  const [test, setTest] = useRecoilState(testState);

  return (
    <div>
      <h4>Test Child</h4>
      <h5>current value: {test}</h5>
      <button type='button' onClick={() => setTest((currentTest) => currentTest + 1)}>
        increment
      </button>
    </div>
  );
}

export default function Test() {
  return (
    <div>
      <h4>Test</h4>
      <RecoilRoot>
        <React.Suspense fallback={<div>Now Loading...</div>}>
          <TestChild />
        </React.Suspense>
      </RecoilRoot>
    </div>
  );
}

動作の解説

上記のコードは下記のような動作となります。

  1. Promiseが未解決の間はReact.Suspenseで設定したフォールバックコンポーネントが表示される。
  2. Promiseが解決されると、TestChildは再レンダリングされる。その際useRecoilStateからはtestが解決された同期的な値として返される。
  3. 初期値が解決する前に新しい値がセットされると、フォールバックは終わり新しい値で再レンダリングされる。
  4. Promiseでエラーが発生した場合は、ErrorBoundaryまでフォールバックされる。

なんでそんな動作になるのか?

recoil 内では初期値がPromiseの場合にuseRecoilValueなどで呼び出されると状態のよって以下のような挙動となることで上記機能を実現している。

1. 状態が未解決なPromiseの場合:useRecoilValuePromisethrowする。

throwされたPromiseSuspenseでキャッチされフォールバックコンポーネントが描写される。Promiseが解決されると再描写される。
また、Promiseが未解決であっても新しい同期的な値をatomにセットすると、Recoilが初期値のPromiseを解決してくれる。

2. 状態が解決済なPromiseの場合:useRecoilValueは解決した同期的な値を返す
3. 状態がRejectされたPromiseの場合:useRecoilValueはエラーをthrow

エラーはErrorBoundaryでキャッチされる。

注意点

Recoil のatomは初期値としてのみPromiseを受け取ることができます。

これで何が嬉しいのか

コンポーネントから状態を減らせる

今まで非同期データを扱う場合、ローディング中かどうかで UI を切り替えるためにisLoadingみたいな状態が必要だったが、それが不要になる。

コンポーネント内で非同期を意識しなくていい

useRecoilValueは解決した値を同期的に返すのでコンポーネント内では非同期を意識する必要がない。

Recoil と React Suspense を使ったサンプル

Recoil と React Suspense で Firebase Authentication の状態を管理するサンプルを作成しました。

https://zenn.dev/riemonyamada/articles/ad38200a1c7fa3

https://zenn.dev/riemonyamada/articles/a55599711fa437

また状態管理ライブラリのjotaiも同じようにSuspenseに対応しています。

https://zenn.dev/riemonyamada/articles/6d6b93139a48a4

Discussion

ログインするとコメントできます