目次
Reactでエラーが発生すると画面が真っ白になってしまうことがあります。Error Boundaryを使うと、エラーが発生してもアプリ全体がクラッシュするのを防ぎ、適切なフォールバックUIを表示できます。
初心者でも「なんとなく使う」ではなく、Error Boundaryが何をしているのかを正確に理解しておくと、いざ問題が起きたときに適切な判断ができるようになります。
Reactでエラーが発生すると何が起きるか
Reactのコンポーネントでエラーが発生すると、そのエラーはコンポーネントツリー全体に波及します。結果として、画面が真っ白になったり、何も表示されない状態になります。
function BuggyComponent() {
// undefinedのプロパティにアクセスしようとしてエラー
const user = undefined;
return <p>{user.name}</p>; // TypeError発生
}
function App() {
return (
<div>
<h1>マイアプリ</h1>
<BuggyComponent /> {/* ここでエラーが発生 */}
<Footer /> {/* このコンポーネントも表示されない */}
</div>
);
}
上記のコードでは、BuggyComponentでエラーが発生すると、Appコンポーネント全体が消えてしまいます。ユーザーには何が起きたのか分からず、アプリが使えなくなってしまいます。
Error Boundaryとは
Error Boundaryは、子コンポーネントで発生したエラーをキャッチし、エラー発生時にフォールバックUIを表示するReactコンポーネントです。
JavaScriptのtry-catchに似ていますが、Error BoundaryはReactコンポーネントのレンダリング中に発生するエラーをキャッチするために特別に設計されています。
Error Boundaryを使うと、エラーが発生した部分だけをフォールバックUIに置き換え、アプリの他の部分は正常に動作し続けることができます。
基本的な実装方法
Error Boundaryはクラスコンポーネントでしか実装できません。getDerivedStateFromErrorまたはcomponentDidCatchのいずれかのライフサイクルメソッドを定義することで、コンポーネントがError Boundaryになります。
関数コンポーネントとHooksが主流となった現在でも、Error Boundaryについては、React本体に関数コンポーネント用のAPIが提供されていないため、クラスコンポーネントで実装する必要があります。これはReact 19でも変わりません。ただし、後述するreact-error-boundaryライブラリを使えば、クラスコンポーネントを直接書く必要はなくなります。
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
// エラー発生時にstateを更新
static getDerivedStateFromError(error) {
return { hasError: true };
}
// エラーログの記録などに使用
componentDidCatch(error, errorInfo) {
console.error('エラーが発生しました:', error);
console.error('エラー情報:', errorInfo.componentStack);
}
render() {
if (this.state.hasError) {
// フォールバックUI
return <h1>問題が発生しました。</h1>;
}
return this.props.children;
}
}
getDerivedStateFromErrorとcomponentDidCatchの違い
これら2つのメソッドは異なる目的で使用します。
getDerivedStateFromError
- レンダリング中に呼ばれる
- フォールバックUIを表示するためにstateを更新する
- 副作用(ログ送信など)は禁止
componentDidCatch
- コミットフェーズで呼ばれる
- エラーログの送信やエラー追跡サービスへの報告に使用
- 副作用を含めることができる
基本的には、getDerivedStateFromErrorでフォールバックUIを表示し、componentDidCatchでエラーをログに記録するという使い分けになります。
使い方
Error Boundaryはエラーをキャッチしたいコンポーネントを囲んで使います。
function App() {
return (
<div>
<h1>マイアプリ</h1>
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
<Footer /> {/* このコンポーネントは影響を受けない */}
</div>
);
}
BuggyComponentでエラーが発生しても、Footerは正常に表示されます。エラーが発生した部分だけが「問題が発生しました。」というメッセージに置き換わります。
Error Boundaryの配置戦略
Error Boundaryをどこに配置するかは、エラー発生時にどの範囲を影響させたいかによって決まります。
アプリ全体を囲む
最もシンプルな方法です。どこでエラーが発生しても、アプリ全体がフォールバックUIに置き換わります。
function App() {
return (
<ErrorBoundary>
<Header />
<Main />
<Footer />
</ErrorBoundary>
);
}
機能ごとに囲む
特定の機能でエラーが発生しても、他の機能には影響を与えません。
function App() {
return (
<div>
<Header />
<ErrorBoundary>
<UserProfile /> {/* ここでエラーが発生しても */}
</ErrorBoundary>
<ErrorBoundary>
<PostList /> {/* こちらは正常に表示される */}
</ErrorBoundary>
<Footer />
</div>
);
}
ユーザーの利便性を考えると、エラーの影響範囲を最小限にするために、機能ごとにError Boundaryを配置することを推奨します。
Error Boundaryがキャッチできないエラー
Error Boundaryは万能ではありません。以下のエラーはキャッチできません。
- イベントハンドラ内のエラー -
onClickなどのイベント内で発生したエラー - 非同期コード -
setTimeout、fetch、Promiseなどの非同期処理内のエラー - サーバーサイドレンダリング - SSR中のエラー
- Error Boundary自身のエラー - Error Boundaryコンポーネント自体で発生したエラー
イベントハンドラ内のエラーは通常のtry-catchで処理します。
function Button() {
const handleClick = () => {
try {
// エラーが発生する可能性のある処理
doSomething();
} catch (error) {
console.error('ボタンクリックでエラー:', error);
// エラー処理
}
};
return <button onClick={handleClick}>クリック</button>;
}
カスタマイズしたフォールバックUI
プロダクションでは、より親切なフォールバックUIを表示することが重要です。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// エラー追跡サービスに送信
logErrorToService(error, errorInfo);
}
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>問題が発生しました</h2>
<p>ご不便をおかけして申し訳ありません。</p>
<button onClick={this.handleRetry}>
再試行
</button>
</div>
);
}
return this.props.children;
}
}
「再試行」ボタンを用意することで、ユーザーが自分でリカバリーを試みることができます。
react-error-boundaryライブラリ
毎回Error Boundaryをクラスコンポーネントで実装するのは手間がかかります。react-error-boundaryというライブラリを使うと、より簡潔に書けます。
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<p>問題が発生しました</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>再試行</button>
</div>
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
);
}
ライブラリを使うことで、フォールバックUIを関数コンポーネントとして定義でき、リセット機能も簡単に実装できます。
開発環境と本番環境の違い
開発環境では、Reactはエラーが発生するとエラーオーバーレイを表示します。これはデバッグを容易にするためですが、Error Boundaryのフォールバックを確認したい場合は一度オーバーレイを閉じる必要があります。
本番環境ではエラーオーバーレイは表示されず、Error Boundaryのフォールバックがそのまま表示されます。
SuspenseとError Boundaryの関係
React 19ではSuspenseとuseフックを使った非同期データの取得が推奨されています。SuspenseとError Boundaryは異なる役割を持ちますが、組み合わせて使うことで、ローディング状態とエラー状態の両方を適切に処理できます。
- Suspense: データ読み込み中のローディング状態を管理
- Error Boundary: エラーが発生した場合のフォールバックUIを表示
<ErrorBoundary fallback={<p>エラーが発生しました</p>}>
<Suspense fallback={<p>読み込み中...</p>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
この構成では、UserProfileがデータを読み込んでいる間はSuspenseのフォールバック(「読み込み中...」)が表示され、エラーが発生した場合はError Boundaryのフォールバック(「エラーが発生しました」)が表示されます。
SuspenseはPromiseをキャッチして待機状態を管理しますが、エラーのキャッチはError Boundaryの役割です。そのため、非同期処理を使うコンポーネントには、SuspenseとError Boundaryの両方を配置することが推奨されます。
参考
Error BoundaryとSuspenseについてより詳しく知りたい場合は、以下のリソースを参照してください。
