第118章:エラーの処理 (1)
この章のテーマはズバリ、
「
use(Promise)でデータ取ってるとき、エラーが出たらどうするの?🤯 ふつうの JavaScript みたいにtry-catchじゃダメなの?」
っていう話です。
React v19 からの「use + Suspense スタイルのデータ取得」では、
エラーの扱い方の考え方がちょっとだけ特殊なので、ここで一度整理します 🧠✨
次の章(119章)で本命の「エラーバウンダリ」をガッツリやるので、
ここでは 「なぜ try-catch がダメっぽいのか」「代わりにどう考えるのか」 をつかむのがゴールです。
1️⃣ まず復習:use(Promise) の世界で何が起こってる?
React 19 の use フックをおさらいします 👀
const data = use(somePromise);somePromiseが pending → コンポーネントは「Promise を throw」する → いちばん近い<Suspense>がそれをキャッチしてfallback(ローディング画面)を表示するsomePromiseが resolve →dataに結果が入り、ふつうにレンダーsomePromiseが reject → コンポーネントは「Error を throw」する → いちばん近い エラーバウンダリ がキャッチして「エラー用 UI」を表示する (Medium)
ポイントは、
「Promise の状態に応じて、コンポーネントが
throwしてる」
ってことです。
これが Suspense(待ち) と エラーバウンダリ(失敗) につながります。
2️⃣ try-catch で囲めばよくない? → 実はうまくいかない 🙅♀️
JavaScript だけの世界なら、よくこんな書き方しますよね 👇
try {
const res = await fetch("/api/data");
const data = await res.json();
} catch (error) {
console.error("エラーだよ", error);
}
じゃあ React コンポーネントで:
// ❌ やりがちな「ダメな例」
import { use } from "react";
import { fetchUsers } from "./api";
export function BadUserList() {
try {
const users = use(fetchUsers()); // ← ここでエラーを try-catch したい…
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
} catch (error) {
// ここに来てほしい…
return <p>読み込みに失敗しました…</p>;
}
}
これ、React 19 では NG 扱い です 🧨
React の公式ドキュメントには、
useをtry-catchの中で呼ぶと「Suspense Exception: This is not a real error!」みたいなエラーになるから、try-catchではなく エラーバウンダリ か Promise の.catchを使ってね
という注意書きがあります。(React)
さらに、公式の ESLint ルールでも、
「レンダー中に起きたエラーは
try-catchでは拾えないから、 エラーバウンダリを使いましょう」
とハッキリ書かれています。(React)
なぜ try-catch じゃダメなの?
- コンポーネントの関数自体を 呼んでいるのは React 本体 だから
- レンダリング中に起きたエラーは、React が「上方向」に伝播させてエラーバウンダリに届ける 仕組みになっているから(Epic React)
なので、私たちがコンポーネントの中で try { const data = use(...) } catch { ... } としても、
その catch にはエラーが落ちてこない、というイメージです 🌀
3️⃣ 図でイメージしよう 🧠✨
use(Promise) の世界で、エラーとローディングがどう流れるかの図です。
- 「待ち」の時 →
Suspenseが担当 - 「失敗」の時 → エラーバウンダリが担当
try-catch の出番はここにはありません 🙅♀️
4️⃣ エラー処理の選択肢はざっくり 2 パターン 🌈
React 19 の use ベースの世界では、だいたい次の 2 パターンで考えます。
✅ パターンA:エラーを「Promise 内側」で処理する(.catch パターン)
「エラーが出ても、エラー画面にはしたくない。 代わりに『データなし』として表示したい」 みたいなケースです。
React 公式の use ドキュメントでも、
「Promise の .catch で別の値に変換する」パターンが紹介されています。(React)
例:エラーのときは「空配列」にしてしまう
// api.ts
export type User = {
id: number;
name: string;
};
export function fetchUsers(): Promise<User[]> {
return fetch("https://example.com/api/users").then((res) => {
if (!res.ok) {
throw new Error("ユーザー取得に失敗しました");
}
return res.json() as Promise<User[]>;
});
}
// UserList.tsx
import { use } from "react";
import type { User } from "./api";
import { fetchUsers } from "./api";
export function UserList() {
// ❗ エラーが出たら空配列にしてしまう
const userPromise: Promise<User[]> = fetchUsers().catch((error) => {
console.error("ユーザー取得エラー", error);
// 「データなし」として扱う
return [];
});
const users = use(userPromise);
if (users.length === 0) {
return <p>ユーザーが見つかりませんでした。</p>;
}
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
この書き方だと:
- ネットワークエラーが起きても Promise の中で
.catchして、 - コンポーネントに届くときには 「ちゃんと resolve 済み」 になっています
- なので
use(userPromise)は エラーを投げず、普通に動く ✅
👉 「エラー画面」ではなく 「データがない通常状態」として扱いたいときにピッタリです。
✅ パターンB:エラーはエラーバウンダリに任せる(次章で本格的に✨)
もうひとつの考え方は、
エラーが起きたら、その部分の UI を「まるごとエラー画面」に切り替えたい 🧱
というパターンです。
そのときは Promise に .catch を付けずに、あえて reject のまま にしておきます。
すると、use が throw した Error が 一番近いエラーバウンダリ に届きます。(Medium)
イメージコード(エラーバウンダリの中身は次章で!)
// UserList.tsx(エラーは握りつぶさない)
import { use } from "react";
import { fetchUsers } from "./api";
export function UserList() {
const users = use(fetchUsers()); // ← エラーが起きたら Error を throw する
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// App.tsx(イメージ)
import { Suspense } from "react";
import { UserList } from "./UserList";
import { AppErrorBoundary } from "./AppErrorBoundary"; // 119〜120章で実装予定
export function App() {
return (
<AppErrorBoundary>
<Suspense fallback={<p>ユーザー一覧を読込中…⏳</p>}>
<UserList />
</Suspense>
</AppErrorBoundary>
);
}
ここでは:
- 読み込み中 →
Suspenseのfallback(ローディング表示) - エラー →
AppErrorBoundaryがキャッチして「ごめんね画面」を表示
というコンビプレイになります ⚾
5️⃣ 「try-catch の代わりに何を使うの?」を整理しよう 📝
React のレンダリング中に起きるエラーについて、ざっくりまとめると:
-
❌ ダメ
- コンポーネントの中で
try { const data = use(...); } catch { ... } - → React 19 では公式ドキュメント的にも非推奨/エラー扱い (React)
- コンポーネントの中で
-
✅ OK 1:Promise 側で処理
fetchSomething().catch(() => 代わりの値を返す)- → コンポーネントには「成功済みの値」として渡す
-
✅ OK 2:エラーバウンダリに任せる
- Promise はそのまま(エラーは reject のまま)
- →
use()が Error を throw → エラーバウンダリがキャッチ
-
✅ OK 3:イベントハンドラの中の
try-catchonClickの中など、ふつうの JS と同じタイミング のものはtry-catchでOK- これは「レンダー中のエラー」とは別物です
6️⃣ ちょこっと練習問題 ✏️💡
時間があるときに、以下をやってみてください:
-
今まで作った
use+Suspenseのサンプル(もしあれば)で、- わざと
fetchの URL を間違えてエラーにする .catchなし → エラーバウンダリでエラー UI(※次章で実装).catchあり → 「データなし」として扱う この2パターンを比べてみる
- わざと
-
どんな画面なら
- 「ちゃんとエラー画面を出したい」か
- 「エラーでも、静かに空の結果として扱いたい」か を、自分なりに考えてメモしてみる 💭
7️⃣ この章のまとめ 🎀
-
React 19 の
use(Promise)では、- pending →
Suspenseが担当 - rejected → エラーバウンダリが担当
- pending →
-
レンダリング中のエラーは、ふつうの
try-catchでは拾えない → エラーバウンダリ か Promise の.catchを使う世界観 ✨(React) -
「エラーを画面に出すか?」「静かに別の値に置き換えるか?」を UI のデザインとして決める のが大事 🎨
次の 第119章 では、 ここでチラ見せした 「エラーバウンダリってなに?どう書くの?」 を 実際のコードでしっかり作っていきます 🧱✨お楽しみに〜! 🎉