React Error Boundary で非同期 / イベントハンドラのエラーがキャッチできない
Error Boundary は子ツリーのレンダリング中・ライフサイクル・コンストラクタで投げられた例外しか捕まえない。
イベントハンドラや非同期 / setTimeout のエラーは別途 try/catch して setState で再投入する必要がある。
#react#error-boundary#async#event-handler#error-handling
公開:
要約
React の Error Boundary が「呼ばれない」と感じるとき、ほぼ全てがイベントハンドラ・非同期処理・SSR 中のエラーで、これらは仕様上 Error Boundary の捕捉対象外。
レンダリング経路に「エラーを再投入」する処理を自分で書く必要がある。
よくある原因
<button onClick={() => { throw new Error("x"); }}>のように、イベントハンドラ内で投げているuseEffect内でawait fetch(...)し、rejectをそのまま放置しているsetTimeout(() => { throw new Error("x"); }, 0)のように、別ティック上で投げている- SSR 中(サーバー実行)に投げたものを Error Boundary に流れると期待している
- Error Boundary 自身(同じコンポーネント)で投げたエラーをキャッチしようとしている
解決策
1. イベントハンドラは try/catch + setState
function Form() {
const [error, setError] = useState<Error | null>(null);
if (error) throw error; // 次のレンダーで Error Boundary に流す
async function onSubmit() {
try {
await submit();
} catch (e) {
setError(e as Error);
}
}
return <button onClick={onSubmit}>送信</button>;
}Error Boundary の捕捉対象はレンダー時の throw のみ。React 公式ドキュメント でも明示されているとおり、この「state にしてから throw」パターンが標準。
2. react-error-boundary のヘルパを使う
import { useErrorBoundary } from "react-error-boundary";
function MyComp() {
const { showBoundary } = useErrorBoundary();
async function load() {
try {
const data = await fetch("/api").then((r) => r.json());
// ...
} catch (e) {
showBoundary(e);
}
}
// ...
}ライブラリが内部で「エラーを state にしてから throw」を吸収してくれる。
3. 非同期エラーをグローバルに拾う
最後の砦として、window.addEventListener("unhandledrejection", ...) でログ収集する。
ただし UI を切り替えたいなら個別に Error Boundary に流すべき。
4. SSR / フレームワーク特有のフックを使う
Next.js の App Router なら error.tsx、Pages Router なら _error.tsx、Remix なら ErrorBoundary export を使う。
クライアントの Error Boundary とは独立した仕組みなので混同しない。