できない.dev

React Error Boundary で非同期 / イベントハンドラのエラーがキャッチできない

Error Boundary は子ツリーのレンダリング中・ライフサイクル・コンストラクタで投げられた例外しか捕まえない。
イベントハンドラや非同期 / setTimeout のエラーは別途 try/catch して setState で再投入する必要がある。

#react#error-boundary#async#event-handler#error-handling

公開:

要約

React の Error Boundary が「呼ばれない」と感じるとき、ほぼ全てがイベントハンドラ・非同期処理・SSR 中のエラーで、これらは仕様上 Error Boundary の捕捉対象外。
レンダリング経路に「エラーを再投入」する処理を自分で書く必要がある。

よくある原因

  1. <button onClick={() => { throw new Error("x"); }}> のように、イベントハンドラ内で投げている
  2. useEffect 内で await fetch(...) し、reject をそのまま放置している
  3. setTimeout(() => { throw new Error("x"); }, 0) のように、別ティック上で投げている
  4. SSR 中(サーバー実行)に投げたものを Error Boundary に流れると期待している
  5. 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 とは独立した仕組みなので混同しない。

この記事は役立ちましたか?