事象
TODOアプリにおいて、supabaseからテストデータを取得させる間にローディング画面が表示されるよう実装したのですが、取得したデータは表示されるもののローディング画面は表示されない、という現象が発生しました。
コード
(App.jsx及びsupabase関係のコンポーネントは省略)
import { useState, useEffect } from 'react';
import { getAllLogs } from '../utils/supabaseFunctions';
export const LogContent = () => {
const [logs, setLogs] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const getLogs = async () => {
const logs = await getAllLogs();
setLogs(logs);
};
getLogs();
setIsLoading(false);
}, []);
return isLoading ? (
<div>Loading...</div>
) : (
<div>
<h2>学習記録一覧</h2>
{logs.map((log) => (
<p key={log.id}>
{log.title} {log.time}時間
</p>
))}
</div>
);
};
原因
useEffect
内での非同期処理の扱いをよく理解しておらず、setIsLoading(false)
が不適切なタイミングで実行されてしまうことが原因でした。
getLogs()
はasync
関数ですが、この関数は非同期処理getAllLogs()
が呼び出されると、結果が帰ってくるまで制御をasync
の呼び出し元の関数に返します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
この場合の呼び出し元はuseEffect
ですから、await getAllLogs()
が完了するのを待つことなく次の行のsetIsLoading(false)
が実行されてしまうことになります。
useEffect(() => {
const getLogs = async () => {
const logs = await getAllLogs();
setLogs(logs);
};
getLogs(); //処理を開始
// await getAllLogs()の結果が返ってくるまで制御がuseEffectに戻る
setIsLoading(false); // 非同期処理の完了を待たずに実行
}, []);
ある処理を実行している間に他の処理を並行して実行するのが非同期処理ですから、これは当然の流れなのですが、非同期処理への理解が浅かったためすぐには原因がわからず、だいぶ悩みました。
対処
try...catch...finally
を使用し、setIsLoading(false)
の実行タイミングを制御することで解決しました。
useEffect(() => {
const getLogs = async () => {
try {
setIsLoading(true); // データ取得開始前にtrueに設定
const fetchedLogs = await getAllLogs();
setLogs(fetchedLogs);
} catch (error) {
console.error("読込エラーです:", error);
} finally {
setIsLoading(false); // データ取得完了後にfalseに設定
}
};
getLogs();
}, []);
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼