レビュー待ち·難易度: 中級·更新: 2026-04-18

React で再試行ボタン付きの ErrorBoundary を実装する

エラー発生時に「再試行」ボタンを表示し、state リセットで再レンダリングを促す ErrorBoundary パターンの実装例。

nextjserror-handling

対応バージョン

nextjs 15react 19

前提環境

React クラスコンポーネントと getDerivedStateFromError の基本を理解していること

概要

エラーが発生した際に fallback UI と「再試行」ボタンを表示し、ボタン押下で key を変えて子コンポーネントを再マウントするリトライ付き ErrorBoundary。ネットワーク一時障害など回復可能なエラーに有効なパターン。

インストール

# 追加インストールは不要

実装

再試行付き ErrorBoundary

// components/RetryErrorBoundary.tsx
import { Component, type ReactNode } from "react";

type Props = {
  children: ReactNode;
  fallback?: (props: { error: Error; retry: () => void }) => ReactNode;
};

type State = {
  hasError: boolean;
  error: Error | null;
  retryKey: number;
};

export class RetryErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false, error: null, retryKey: 0 };
  }

  static getDerivedStateFromError(error: Error): Partial<State> {
    return { hasError: true, error };
  }

  // retryKey を増やすと子コンポーネントが再マウントされる
  retry = () => {
    this.setState((prev) => ({
      hasError: false,
      error: null,
      retryKey: prev.retryKey + 1,
    }));
  };

  render() {
    if (this.state.hasError && this.state.error) {
      if (this.props.fallback) {
        return this.props.fallback({
          error: this.state.error,
          retry: this.retry,
        });
      }

      // デフォルト fallback
      return (
        <div className="rounded border border-red-200 bg-red-50 p-6">
          <p className="mb-2 text-sm font-medium text-red-700">
            エラーが発生しました
          </p>
          <p className="mb-4 font-mono text-xs text-red-500">
            {this.state.error.message}
          </p>
          <button
            onClick={this.retry}
            className="rounded bg-red-600 px-4 py-2 text-sm text-white hover:bg-red-700"
          >
            再試行
          </button>
        </div>
      );
    }

    // retryKey を key に渡すことで変化時に子を再マウント
    return (
      <div key={this.state.retryKey}>{this.props.children}</div>
    );
  }
}

エラーを起こすコンポーネント(デモ用)

// components/UnstableComponent.tsx
"use client";

import { useState } from "react";

export function UnstableComponent() {
  const [shouldThrow, setShouldThrow] = useState(false);

  if (shouldThrow) {
    throw new Error("ネットワークエラー: データの取得に失敗しました");
  }

  return (
    <div className="rounded border p-6">
      <p className="mb-4 text-sm text-gray-700">コンポーネントが正常に表示されています</p>
      <button
        onClick={() => setShouldThrow(true)}
        className="rounded bg-gray-800 px-4 py-2 text-sm text-white hover:bg-gray-700"
      >
        エラーを発生させる
      </button>
    </div>
  );
}

デフォルト fallback を使う例

// app/page.tsx
"use client";

import { RetryErrorBoundary } from "@/components/RetryErrorBoundary";
import { UnstableComponent } from "@/components/UnstableComponent";

export default function Page() {
  return (
    <main className="mx-auto max-w-lg p-8">
      <h1 className="mb-6 text-2xl font-bold">再試行 ErrorBoundary デモ</h1>

      <RetryErrorBoundary>
        <UnstableComponent />
      </RetryErrorBoundary>
    </main>
  );
}

カスタム fallback を使う例

// app/custom/page.tsx
"use client";

import { RetryErrorBoundary } from "@/components/RetryErrorBoundary";
import { UnstableComponent } from "@/components/UnstableComponent";

export default function Page() {
  return (
    <main className="mx-auto max-w-lg p-8">
      <RetryErrorBoundary
        fallback={({ error, retry }) => (
          <div className="rounded-xl bg-yellow-50 p-6 text-center">
            <p className="mb-1 text-base font-bold text-yellow-800">
              データを読み込めませんでした
            </p>
            <p className="mb-4 text-xs text-yellow-600">{error.message}</p>
            <button
              onClick={retry}
              className="rounded-full bg-yellow-500 px-6 py-2 text-sm font-medium text-white hover:bg-yellow-600"
            >
              もう一度試す
            </button>
          </div>
        )}
      >
        <UnstableComponent />
      </RetryErrorBoundary>
    </main>
  );
}

ポイント

  • retryKey を state に持ち、再試行ボタンで +1 することで子コンポーネントが再マウントされる。key が変わると React は古いツリーを破棄して新しくマウントするため、エラー状態がリセットされる
  • fallback を render prop(関数)として受け取ることで、呼び出し元が errorretry を受け取りカスタム UI を自由に定義できる
  • react-suspense-boundary との違い: react-suspense-boundaryReact.lazy のロード失敗を扱う。このパターンはレンダリング中やイベントハンドラ以外でスローされた一般的なエラーを捕捉してリトライ導線を提供する
  • ErrorBoundary はイベントハンドラ内のエラー(onClick など)を捕捉しない。イベントハンドラ内のエラーは try/catch で処理する
  • componentDidCatchconsole.error や外部エラー監視サービス(Sentry など)のレポートを追加すると本番運用に向く

注意点

react-suspense-boundary は React.lazy のロード失敗を Suspense + ErrorBoundary で扱うパターン。react-error-boundary-component は基本的なエラー捕捉のみ。これは「再試行」ボタンで state をリセットし、子コンポーネントを再マウントして回復を試みる導線に特化。

関連サンプル