エラーバウンダリは、中のコンポーネントのレンダリング中に投げられたエラーを捕まえ、アプリ全体をクラッシュさせる代わりにフォールバックのUIを表示するReactのコンポーネントです。そのためのフックはないので、static getDerivedStateFromError を定義したクラスコンポーネントでなければなりません。1回書けば、ツリーのどの部分の周りにも置けます。
「Break the profile」を押すと、子がレンダリング中に例外を投げます。
プロフィールはフォールバックに置き換えられ、ページのほかの部分は動き続けます。コンソールにはエラーが2回表示されます。Reactは捕まえたすべてのエラーを console.error で記録し、componentDidCatch がもう一度記録するからです。「Try again」は原因(broken)とバウンダリのエラーをクリアするので、プロフィールがまたレンダリングされます。reset から this.props.onReset(); を削除すると、何も原因を直していないので、Try againはすぐにまたフォールバックに戻ってしまいます。
クラスの仕組み
2つのメソッドが、クラスをエラーバウンダリにします。
static getDerivedStateFromError(error)は、子が例外を投げた直後、レンダリング中に実行されます。バウンダリをフォールバックに切り替えるstateの更新を返します。純粋でなければならず、ログもリクエストも禁止です。componentDidCatch(error, info)は、フォールバックが画面に出た後に実行されます。エラーをレポートのサービスに送るような副作用に使います。info.componentStackは、例外を投げたコンポーネントとその上のコンポーネントを列挙します。
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error }; // switch to the fallback
}
componentDidCatch(error, info) {
reportError(error, info.componentStack); // your logging service
}
render() {
if (this.state.error) return this.props.fallback;
return this.props.children;
}
}
フックの同等品はないので、関数コンポーネントだけで書いたアプリでも、このクラスが1つは必要です。クラスの構文になじみがなければ、クラスコンポーネントのページで説明しています。
バウンダリが捕まえるものと捕まえないもの
バウンダリが捕まえるのは、Reactがその内側のコンポーネントを処理している間に起きたエラーです。
- レンダリング(関数コンポーネントの本体、クラスの
render) - 子のクラスコンポーネントのライフサイクルメソッドとコンストラクタ
- エフェクト(
useEffect、useLayoutEffect)とrefコールバック - useで読んだrejectされたPromise。React 19では、フォームアクションや
useTransitionのstartTransitionに渡した関数が投げたエラーも
次のものは捕まえません。
- イベントハンドラのエラー
- 自分で始めた非同期のコード:
setTimeout、.then()のコールバック、イベントハンドラの中のawait - バウンダリ自身の
renderのエラー(1つ上のバウンダリが捕まえます) - サーバーレンダリング中のエラー
イベントハンドラが除かれているのは、レンダリングを壊さないからです。画面は正しい状態のままなので、ハンドラの中の try/catch で十分です。それでもバウンダリを表示したいなら、エラーをstateに保存し、次のレンダリングで投げます。
1つ目のボタンではボタンが画面に残り、コンソールに捕まえられなかったエラーが表示されるだけです。2つ目はrejectを捕まえて保存し、バウンダリが引き継ぎます。
バウンダリを置く場所
アプリ全体を1つのバウンダリで包めば真っ白な画面は防げますが、どんな小さなエラーでもすべてが置き換えられてしまいます。サイドバーのウィジェット、チャート、各ルートのように、独立して失敗しうる部分の周りにバウンダリを置いてください。そうすれば失敗してもユーザーが失うのはページではなく1つのパネルで済みます。
フォールバックを表示するのはStocksのパネルだけです。2つのウィジェットを1つの <ErrorBoundary> に入れると、動いているWeatherのパネルも一緒に消えます。
バウンダリがまったくないと、レンダリングのエラーが捕まえられなかったとき、Reactはツリー全体を画面から取り除きます。間違ったデータを表示する壊れたページよりは空のページのほうが安全ですが、ユーザーはそれをクラッシュとして受け取ります。
react-error-boundaryライブラリ
react-error-boundary パッケージはクラスを包み、リセットの補助機能を加えてくれます。このエディタでは使えないので、静的なブロックで示します。
import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary';
function Fallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>Something went wrong: {error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
<ErrorBoundary FallbackComponent={Fallback} onReset={() => refetch()} resetKeys={[userId]}>
<Profile userId={userId} />
</ErrorBoundary>;
// Inside a child: hand an async error to the boundary
const { showBoundary } = useErrorBoundary();
fetchData().catch(showBoundary);
resetKeys は、並べた値のどれかが変わるとバウンダリをリセットし、showBoundary は前の例の「保存して投げる」技を行います。
React 19でのエラーの報告
React 19はエラーを新しい方法で報告します。バウンダリが捕まえたエラーは console.error で1回記録され、捕まえられなかったエラーは window.reportError に送られます。自分のサービスに送るには、createRoot にオプションを渡します。各オプションは、対応するデフォルトのログを置き換えます。
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'), {
onCaughtError(error, errorInfo) {
// an error boundary caught it
logToService('caught', error, errorInfo.componentStack);
},
onUncaughtError(error, errorInfo) {
// no boundary caught it, and React removed the tree
logToService('uncaught', error, errorInfo.componentStack);
},
onRecoverableError(error, errorInfo) {
// React recovered by itself, for example after a hydration mismatch
logToService('recovered', error, errorInfo.componentStack);
},
});
root.render(<App />);
onRecoverableError は、ハイドレーションの不一致や、Reactが再試行したら消えたレンダリングのエラーのように、Reactが手助けなしに直したエラーのためのものです。hydrateRoot も同じ3つのオプションを受け付けます。
よくある間違い
例外を投げるコンポーネントの中にバウンダリを置く。バウンダリが捕まえるのは子からのエラーだけです。Profile が自分のJSXの周りに <ErrorBoundary> を描画し、自分の本体で例外を投げると、エラーはそれを通り越して1つ上のバウンダリに行きます。コンポーネントはその親から包んでください。
getDerivedStateFromErrorで副作用を起こす。これはレンダリング中に実行され、複数回実行されることもあるので、stateを返すだけにしてください。ログは componentDidCatch か、ルートの onCaughtError に書きます。
何も直さないリセット。バウンダリのエラーをクリアすると、同じpropsで同じ子が描画されます。それがまた例外を投げれば、ユーザーはフォールバックに戻ってしまいます。最初の例が onReset でしているように、まずエラーの原因を変え(再取得する、不正な入力をクリアする、key を変える)、それからエラーをクリアしてください。
アプリ全体に1つのバウンダリだけ。真っ白なページは防げますが、小さな失敗がすべてページ全体のエラーになります。機能ごとにバウンダリを追加し、トップレベルのものは最後の砦として残してください。
自分にもエラーを隠してしまうフォールバック。ユーザーには親切なメッセージが正解ですが、エラーが自分のログに届くことは確かめてください。React 19は捕まえたエラーをデフォルトでコンソールに記録しますが、本番ではそのコンソールはユーザーのものなので、componentDidCatch か onCaughtError からサービスにエラーを送ってください。
エラーバウンダリは <Suspense> と自然に組み合わさります。Suspenseが「まだ準備ができていない」を、バウンダリが「失敗した」を扱います。use の例のようにバウンダリをSuspenseの外側に置けば、1つのコンポーネントで読み込み、成功、失敗のすべてをカバーできます。
よくある質問
Reactのエラーバウンダリとは何ですか?
中にあるどれかのコンポーネントのレンダリング中に投げられたJavaScriptのエラーを捕まえ、代わりにフォールバックのUIを描画するコンポーネントです。エラーバウンダリがないと、レンダリング中のエラーでアプリ全体が画面から消えます。
エラーバウンダリのためのフックはありますか?
いいえ。エラーバウンダリは static getDerivedStateFromError(と、必要なら componentDidCatch)を持つクラスコンポーネントでなければなりません。1回書けば、関数コンポーネントからほかのコンポーネントと同じように使えます。あるいは、クラスを包んでくれる react-error-boundary をインストールしてください。
エラーバウンダリはイベントハンドラのエラーを捕まえますか?
いいえ。イベントハンドラはレンダリングの外で実行されるので、Reactが回復するためにバウンダリは必要ありません。ハンドラで try/catch を使い、バウンダリを表示したいならエラーをstateに保存してレンダリング中に投げてください。
エラーバウンダリは非同期のエラーを捕まえますか?
自分で始めた setTimeout、Promiseのコールバック、fetch().then() のエラーは捕まえません。use で読んだrejectされたPromiseは捕まえ、React 19ではフォームアクションや useTransition の startTransition に渡した関数の中で投げられたエラーも捕まえます。
エラーバウンダリをリセットするには?
エラーの原因を直した後で、そのエラーのstateをクリアします。通常はフォールバックの中のTry againボタンから行います。バウンダリの key を変えてもリセットされます。Reactが新しいインスタンスをマウントするからです。