Day 4・useEffect と データ取得 — 外の世界とつながる
読み込み中・成功・失敗の3状態を設計する
データ取得には必ず「待ち」と「失敗」がある。loadingとerrorを状態として持ち、ユーザーに伝えるUIを作る。
取得には必ず3つの状態がある
前のレッスンではデータを取って表示するところまでやりました。でも実際のアプリでは、それだけでは不十分です。通信には時間がかかるので、データがまだ来ていない「読み込み中」の時間が必ずあります。さらに、サーバーが落ちていたり電波が悪かったりで「失敗」することもあります。つまりデータ取得は、読み込み中・成功・失敗という3つの状態を必ず通ります。この3つをきちんと扱うのが、実務の定番の考え方です。
たとえるなら、ネット通販で注文したときの状態と同じです。「配送中(まだ届いていない)」「到着(受け取れた)」「配送トラブル(届かなかった)」の3つがあり、アプリはそれぞれに応じた表示をするべきです。配送中なのに真っ白な画面を見せたら、ユーザーは壊れたのかと不安になります。だから、いま自分がどの状態にいるかを画面に出すことが大切です。
loadingとerrorをstateとして持つ
3状態を扱うには、データを入れる箱に加えて、読み込み中かどうかを表すloadingと、失敗した理由を入れるerrorという状態を用意します。取得を始めるときにloadingをtrueにし、成功したらデータを入れてloadingをfalseに、失敗したらerrorに理由を入れます。この3つのstateの組み合わせで、いまどの状態かが表現できます。
import { useEffect, useState } from "react";
function UserCard({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`)
.then((r) => {
if (!r.ok) throw new Error("取得に失敗しました");
return r.json();
})
.then((data) => setUser(data))
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <p>読み込み中...</p>;
if (error) return <p>エラー: {error}</p>;
return <p>{user.name}</p>;
}response.okのチェックを忘れない
fetchはサーバーが404や500を返しても、通信自体が届いていれば「成功」として.then()に進んでしまいます。だからr.okを確認し、失敗ステータスなら自分でthrowしてcatchに送る必要があります。これを忘れると、エラーページのHTMLをデータだと思い込んで表示が崩れる、といった事故になります。
状態管理あり・なしを比べる
状態管理なし
function UserCard({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((r) => r.json())
.then(setUser);
}, [userId]);
// 取得中は user が null で user.name が落ちる
// 失敗しても永遠に空白のまま、原因もわからない
return <p>{user.name}</p>;
}状態管理あり
function UserCard({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// ...fetchでloading/error/userを更新...
if (loading) return <p>読み込み中...</p>;
if (error) return <p>エラー: {error}</p>;
return <p>{user.name}</p>;
}状態管理なしの左側は、取得が終わる前はuserがnullなのにuser.nameを読もうとして画面が壊れます。失敗しても真っ白なまま何も起きず、ユーザーは原因がわかりません。右側のように3状態を分けると、待っている間は「読み込み中」を、失敗したら理由を、成功したら中身を、それぞれ確実に見せられます。この差が、動くだけのアプリと使えるアプリの分かれ目です。
なぜ3状態を意識するのか
ユーザーにとって最も不安なのは、何が起きているかわからない沈黙です。読み込み中と伝えれば待てますし、失敗と理由を伝えれば再試行できます。3状態の設計は技術というより、待たせている相手への配慮です。実務ではこの分岐が当たり前の作法として求められます。
AIにはこう聞く
「このデータ取得コンポーネントに、loading・error・successの3状態を持たせて。fetchのr.okチェックとcatch、finallyでのloading解除も入れて」と頼むと、実務的な形にしてくれます。生成後は、失敗時に本当にerrorへ入るか、loadingが必ずfalseに戻るかを確認しましょう。
読んでも腑に落ちない所は、AIに噛み砕いてもらいましょう。
理解度チェック
答えられれば、面接で説明できる状態に一歩近づきます。
データ取得で必ず考えるべき3つの状態はどれ?
fetchでサーバーが404を返したとき、そのままだと何が起きますか?
loadingとerrorの状態を持たないと、どんな不便が起きるか説明してください。
記述式です。まず自分の言葉で答えを考えてから、模範解答を開いて自己採点してください。