ローディング・エラー処理がなぜ「実務で必須」なのか
アプリが「ちゃんと動く」ことと、「ちゃんと使える」ことは別物です。
API からデータを取ってくるような画面では、必ずこういう瞬間が生まれます。
データを取りに行っている最中
サーバーが落ちている、ネットワークが不安定などで失敗したとき
このときに何も表示しない、あるいは真っ白のままだと、
ユーザーは「壊れているのかな?」と不安になります。
だからこそ、
今、読み込み中ですよ
エラーが起きたので、こういう状態ですよ
をきちんと画面に出すことが、実務ではほぼ必須です。
これは「技術」以前に、UX(ユーザー体験)の基礎です。
ローディング表示の基本
状態を一つ増やすだけで世界が変わる
前回の「データ取得(fetch)」の話を思い出してください。
データを state に持つだけでなく、isLoading という state を一つ足すだけで、
ローディング表示はかなり自然に書けます。
シンプルな例から見てみましょう。
"use client";
import { useEffect, useState } from "react";
type User = {
id: number;
name: string;
};
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function fetchUsers() {
setIsLoading(true);
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data: User[] = await response.json();
setUsers(data);
setIsLoading(false);
}
fetchUsers();
}, []);
if (isLoading) {
return <p>ユーザー情報を読み込み中です...</p>;
}
return (
<main>
<h1>ユーザー一覧</h1>
{users.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</main>
);
}
TSXここでやっていることは、とてもシンプルです。
コンポーネントが表示されたら isLoading を true にする
fetch が終わってデータを setUsers したら、isLoading を false にする
レンダリング時に isLoading を見て、「読み込み中ならローディング表示」「そうでなければ本来の画面」を出し分ける
この「状態を一つ増やして、条件分岐で UI を切り替える」という発想が、
ローディング表示の基本パターンです。
ローディング表示を少しだけ丁寧にする
実務では、単なるテキストだけでなく、
「どこが読み込み中なのか」をもう少し分かりやすく見せることが多いです。
例えば、カードの中だけローディングにするイメージです。
export default function UsersCard() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function fetchUsers() {
setIsLoading(true);
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data: User[] = await response.json();
setUsers(data);
setIsLoading(false);
}
fetchUsers();
}, []);
return (
<section
style={{
backgroundColor: "white",
padding: "16px 20px",
borderRadius: "8px",
boxShadow: "0 1px 3px rgba(15,23,42,0.08)",
}}
>
<h2>ユーザー一覧</h2>
{isLoading ? (
<p style={{ color: "#6b7280" }}>読み込み中です...</p>
) : (
users.map((user) => <p key={user.id}>{user.name}</p>)
)}
</section>
);
}
TSXページ全体を止めるのではなく、
「このカードの中だけ読み込み中」と分かるようにすることで、
ユーザーは「アプリ全体が固まった」とは感じにくくなります。
エラー時の表示
エラーも「状態」として扱う
ローディングと同じように、エラーも state として持つのが基本です。
const [error, setError] = useState<string | null>(null);
TSXこれを使って、成功・失敗を分けていきます。
"use client";
import { useEffect, useState } from "react";
type User = {
id: number;
name: string;
};
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchUsers() {
try {
setIsLoading(true);
setError(null);
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("ユーザー情報の取得に失敗しました");
}
const data: User[] = await response.json();
setUsers(data);
} catch (e) {
setError(e instanceof Error ? e.message : "不明なエラーが発生しました");
} finally {
setIsLoading(false);
}
}
fetchUsers();
}, []);
if (isLoading) {
return <p>ユーザー情報を読み込み中です...</p>;
}
if (error) {
return <p style={{ color: "red" }}>エラー: {error}</p>;
}
return (
<main>
<h1>ユーザー一覧</h1>
{users.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</main>
);
}
TSXここでのポイントは三つです。
try / catch / finally で「成功」「失敗」「後片付け」を分けている
エラーが起きたら setError(...) でメッセージを state に保存する
レンダリング時に error を見て、「エラー時専用の UI」を出す
「エラーが起きたら console にだけ出す」のではなく、
ユーザーにも分かる形で画面に出すことが大事です。
エラー表示の UX を少しだけ良くする
実務では、エラー表示も「ただ赤字で出す」だけでは足りないことが多いです。
例えば、次のような工夫がよくあります。
エラー文をやさしい日本語にする
「もう一度試す」ボタンを付ける
どの部分のエラーか分かるようにする(ページ全体なのか、一部のカードなのか)
簡単な「再試行ボタン付き」の例を出してみます。
export default function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function fetchUsers() {
try {
setIsLoading(true);
setError(null);
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("ユーザー情報の取得に失敗しました");
}
const data: User[] = await response.json();
setUsers(data);
} catch (e) {
setError(e instanceof Error ? e.message : "不明なエラーが発生しました");
} finally {
setIsLoading(false);
}
}
useEffect(() => {
fetchUsers();
}, []);
if (isLoading) {
return <p>ユーザー情報を読み込み中です...</p>;
}
if (error) {
return (
<main>
<h1>ユーザー一覧</h1>
<p style={{ color: "red" }}>エラー: {error}</p>
<button onClick={fetchUsers}>もう一度読み込む</button>
</main>
);
}
return (
<main>
<h1>ユーザー一覧</h1>
{users.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</main>
);
}
TSX「失敗したら終わり」ではなく、
「失敗したけど、こうすればやり直せます」と示してあげることで、
ユーザーのストレスはかなり減ります。
UXの基礎としてのローディング・エラー
ユーザーは「待たされること」より「何が起きているか分からないこと」が嫌い
技術者目線だと「いかに速くするか」に意識が向きがちですが、
ユーザー目線では、
今、何が起きているのか分からない
このまま待てばいいのか、エラーなのか分からない
という状態が一番ストレスになります。
だからこそ、
読み込み中なら「読み込み中」と出す
エラーなら「エラー」と出す
その上で、可能なら「どうすればいいか」も添える
というのが UX の基礎です。
「状態を増やして UI を分ける」という発想
ここまでの話を抽象化すると、こうなります。
データ取得の画面には、少なくとも次の状態がある。
まだ何もしていない(初期)
読み込み中
成功してデータがある
エラーが起きた
これを、state で表現してあげる。
isLoadingerrordata
そして、レンダリング時に
どの状態かを if / 三項演算子で判定し
それぞれに対応する UI を出し分ける
という構造にする。
この「状態を増やして UI を分ける」という考え方は、
フォーム、モーダル、ログイン状態など、あらゆる場面で効いてきます。
実務で必須の考え方
「ハッピーケースだけで終わらせない」
学習中はどうしても「うまくいくパターン」だけを作りがちです。
でも実務では、
API が落ちている
レスポンスが遅い
ユーザーのネットが不安定
一部のデータだけ壊れている
といった「うまくいかないケース」が日常茶飯事です。
そのときに、
何も表示されない
真っ白な画面になる
コンソールにだけエラーが出ている
という状態だと、「壊れているアプリ」になってしまいます。
だからこそ、実務では
ローディング表示を必ず用意する
エラー表示を必ず用意する
どちらも「ユーザーに意味が伝わる文言」にする
というのが「最低限のライン」になります。
Next.js なら「サーバー側のエラー」「クライアント側のエラー」も意識する
少し先の話ですが、Next.js では
サーバーコンポーネントでの fetch 失敗
クライアントコンポーネントでの fetch 失敗
など、エラーの発生場所も分かれてきます。
App Router には error.tsx や loading.tsx という仕組みもあり、
ルート単位で「エラー時の画面」「ローディング時の画面」を定義できます。
今はまだ「コンポーネント内でのローディング・エラー処理」に集中してOKですが、
頭の片隅に
Next.js にはルート単位のエラー・ローディング専用ファイルもある
くらいは置いておくと、次のステップに進みやすくなります。
まとめと小さな宿題
ここまでをまとめると、
ローディング表示は、isLoading という state を用意し、「読み込み中なら専用の UI を出す」という形で実装する。
エラー処理は、error という state を用意し、try / catch でエラーを捕まえてメッセージを保存し、「エラー時専用の UI」を出す。
UX の観点では、「速さ」だけでなく「今何が起きているかをユーザーに伝えること」が非常に重要で、ローディング・エラー表示はその最初の一歩。
実務では「ハッピーケースだけで終わらせない」ことが必須であり、ローディング・エラーをきちんと設計できるかどうかが、React / Next.js エンジニアとしての大きな分かれ目になる。
小さな宿題として、
前回作った「記事一覧の fetch コンポーネント」にisLoading と error を追加して、
読み込み中・エラー時・成功時の UI を自分なりにデザインしてみる
というのをやってみてほしいです。
「失敗したときにどう振る舞うか」を考え始めた瞬間から、コードは一気に“実務寄り”になっていきます。

