はじめに
以前、useEffect
とSuspense
を使って失敗したので、そのリベンジで今回はライブラリ使わずにuse
とSuspense
使ってSupabaseからデータ表示するぞ!とチャレンジしてみたらだいぶ苦戦したので、ここに記録しておきます。
事象
下記のコードでuse
とSuspense
を使用してSupabaseからのデータ表示を試みたところ、ローディング画面のまま遷移せず、コンソールを開くと無限ループが発生していました。
関連コード
Supabaseからのデータ取得
import { Record } from "../domain/record";
import { supabase } from "../utils/supabase";
export async function GetRecords(): Promise<Record[]> {
const response = await supabase.from("study-record").select("*");
if (response.error) {
throw new Error(response.error.message);
}
const recordsData = response.data.map((record) => {
return new Record(record.id, record.title, record.time);
});
console.log("recordsData", recordsData);
return recordsData;
}
App.tsx
import { Suspense } from "react";
import { RecordList } from "./components/RecordList";
import { ErrorBoundary } from "react-error-boundary";
function App() {
return (
<>
<ErrorBoundary fallback={<div>エラーが発生しました</div>}>
<Suspense fallback={<div>読み込み中...</div>}>
<RecordList />
</Suspense>
</ErrorBoundary>
</>
);
}
export default App;
import { use } from "react";
import { GetRecords } from "../lib/record";
export const RecordList = () => {
const records = use(GetRecords());
return (
<div>
{records.map(record => (
<div key={record.id}>
<h2>{record.title}</h2>
<p>{record.time}</p>
</div>
))}
</div>
);
};
対処
結論から言うと、use
とSuspense
の仕様をよくわかっていなかったのが原因でした。
Suspense
は、Promise
が解決するとコンポーネントを再レンダリングします。
しかし、今回のコードでは再レンダリングされる度にSupabaseからのデータ取得関数であるGetRecords()
が実行されるため、その都度新たなPromiseが作成されて無限ループが発生、いつまでも再レンダリングが完了しない、ということになっていました。
そこで、Promise
を一度だけ作成しキャッシュするカスタムフックを作成し、それを呼び出すことで解決しました。
import { use } from "react";
import { GetRecords } from "../lib/record";
import { Record } from "../domain/record";
let recordsPromise: Promise<Record[]> | null = null;
export const useFetchData = (): { records: Record[] } => {
if (!recordsPromise) {
recordsPromise = new Promise<Record[]>((resolve) => {
(async () => {
const data = await GetRecords();
resolve(data);
})();
});
}
const records = use(recordsPromise);
return { records };
};
import { useFetchData } from "../hooks/useFetchData";
export const RecordList = () => {
const { records } = useFetchData();
console.log("records", records);
return (
<div>
{records.map((record) => (
<div key={record.id}>
<h2>{record.title}</h2>
<p>{record.time}</p>
</div>
))}
</div>
);
};
まとめ
今回は、use
とSuspense
の理解不足に起因するエラーでした。
解決するにあたって、Suspense
に渡す「キャッシュ済みPromise
」というものがどういうものかも理解しておらず、Promise
についての理解も不足していることに気付きました。
非同期処理周りの理解をもっと深めることが必要だと痛感しました。
参考