副作用の中で fetch を使ってリモートデータを読み込み、ローディングとエラーの状態をきれいにモデリングします。
このレッスンを Kodokon で開くネットワークリクエストには時間がかかり、失敗する可能性もあります。インターフェイスはそうした現実を反映しなければなりません。プロの慣習は、3 つの異なる state を追跡することです。受け取ったデータ、ローディングを表す真偽値、そして起こりうるエラーです。サーバーに接続するのは副作用なので、リクエストは useEffect の中に入れます。
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.example.com/users")
.then((res) => {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.json();
})
.then((data) => setUsers(data))
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);レンダリングでは、ケースを順番に処理します。まずローディング、次にエラー、最後にデータです。これらの早期リターンはコードを読みやすく保ち、ユーザーが常に一貫した何か、つまり待機中のインジケーター、失敗のメッセージ、または期待されるリストを見ることを保証します。
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);最後の落とし穴です。依存関係(例えば userId)が変わると、古いリクエストがまだ処理中かもしれないうちに新しいリクエストが送られます。もし古い方が最後に応答すると、新しいデータを上書きしてしまいます。定番の対策は、クリーンアップ関数で true に設定する ignore フラグで、古いレスポンスを捨てることです。
useEffect(() => {
let ignore = false;
setLoading(true);
fetch("/api/users/" + userId)
.then((res) => res.json())
.then((data) => {
if (!ignore) setUser(data);
})
.finally(() => {
if (!ignore) setLoading(false);
});
return () => {
ignore = true;
};
}, [userId]);catch が発動しません。なぜですか?ignore フラグは、何のために使われますか?