🐳
Recoil で非同期データを扱う [Suspense, Loadable]
recoil では非同期データの扱い方としてReact.Suspense
を利用する方法と、Loadable
を利用する方法が用意されています。
それぞれ用途によって使い分けられますがどっちも便利です。
React.Suspense
と使う
React.Suspense
と使えば非同期データが非同期であることを意識することなく扱えます。
atom
, selector
にセットされたPromise
が未解決中はReact.Suspense
で指定したフォールバックコンポーネントが描画されます。そのためコンポーネント内では同期データとして扱うことができます。
別途フォールバックコンポーネントの設定は必要ですが、コードを分離できる上に読み込み状態という無駄な状態をコンポーネントから排除できます。
React.Suspense
使用の嬉しい点
- コンポーネント内で未解決時の処理などが不要
- コンポーネント内ではデータを同期的に扱える
React.Suspense
の使用例
import { atom } from 'recoil';
export const testState = atom<string>({
key: 'test',
default: new Promise<string>((resolve) => {
setTimeout(() => {
resolve('test');
}, 10 * 1000);
}),
});
import * as React from 'react';
import { RecoilRoot, useRecoilValue } from 'recoil';
import { testState } from '@src/stores/test';
function TestChild() {
const test = useRecoilValue(testState);
return (
<div>
<h4>Test Child</h4>
<h5>current value: {test}</h5>
</div>
);
}
export function Test() {
return (
<div>
<h4>Test</h4>
<RecoilRoot>
<React.Suspense fallback={<div>Now Loading...</div>}>
<TestChild />
</React.Suspense>
</RecoilRoot>
</div>
);
}
Loadable
を使う
recoil にはLoadable
というデータの状態を表現するためのクラスがあります。
atom
やselector
の値をLoadable
オブジェクトとして受け取ることもできます。
Loadable
は現在のデータの状態を表すstate
とデータの内容であるcontents
というプロパティを持っています。
- データが未解決の場合:
state
はloading
であり、contents
にはPromise
が入っている。 - データが解決済の場合:
state
はhasValue
であり、contents
には解決済みの値が入っている。 - エラーがあった場合:
state
はhasError
であり、contents
にはError
オブジェクトが入っている。
Suspense のフォールバックコンポーネントでは表現が難しい場合や、データの状態によって UI を細かく変化させたり、データの状態によって何か処理をしたい場合はLoadable
を使えます。
Loadable
使用の嬉しい点
- すでに
state
が準備されているので同期的にコンポーネントを操作できる。 - すでに
state
が準備されているので自前で状態を作らなくて良い。
Loadable
の使用例
import { RecoilRoot, useRecoilValueLoadable } from 'recoil';
import { testState } from '@src/stores/test';
function TestChild() {
const testLoadable = useRecoilValueLoadable(testState);
if (testLoadable.state === 'hasValue') {
return (
<div>
<h4>Test Child</h4>
<h5>
current value:
{testLoadable.contents}
</h5>
</div>
);
}
if (testLoadable.state === 'loading') {
return <div>Loading...</div>;
}
if (testLoadable.state === 'hasError') {
throw testLoadable.contents;
}
throw new ExhaustiveError(testLoadable); // 網羅性チェック
}
export function Test() {
return (
<div>
<h4>Test</h4>
<RecoilRoot>
<TestChild />
</RecoilRoot>
</div>
);
}
Discussion