🌀

React: アプリ内のエラーを処理する(Error Boundary)

2022/02/07に公開

はじめに

今回はReactアプリケーションでのエラー処理方法について調べてみました。
Reactアプリケーションで発生したエラーはError Boundaryで処理することができるようです。
このError Boundaryを使用したエラー処理方法についてまとめてみました。

React:Error Boundary
https://ja.reactjs.org/docs/error-boundaries.html

環境

  • Windows11
  • vite: v2.7.2
  • node: v16.13.2
  • react: v17.0.2
  • typescript: v4.4.4

エラーが発生していないサンプルプログラムを作成する

まずは エラーが発生していないサンプルプログラムを作成します。

ソースコードを表示
main.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)
components/App.tsx
import Page1 from './Page1'
import Page2 from './Page2'
import Page3 from './Page3'

function App() {
  return (
    <>
      <Page1 />
      <Page2 />
      <Page3 />
    </>
  )
}
export default App
components/Page1.tsx
function Page1() {
  return (
    <div style={{ backgroundColor: '#31C2CF' }}>
      <h3>Page1</h3>
    </div>
  )
}
export default Page1
components/Page2.tsx
function Page2() {
  return (
    <div style={{ backgroundColor: '#D9A0A4' }}>
      <h3>Page2</h3>
    </div>
  )
}
export default Page2
components/Page3.tsx
import Page3Child from './Page3Child'

function Page3() {
  return (
    <div style={{ backgroundColor: '#DFD35F' }}>
      <h3>Page3</h3>
      <Page3Child />
    </div>
  )
}
export default Page3
components/Page3Child.tsx
function Page3Child() {
  const title = 'Page3Child'

  return (
    <div style={{ backgroundColor: '#DEB331' }}>
      <h5>{title}</h5>
    </div>
  )
}
export default Page3Child

コンポーネント内でエラーを発生させてみる

次にコンポーネント内でエラーを発生させたときの動作を確認してみます。
エラーが発生するようPage3Childコンポーネントのコードを修正します。

components/Page3Child.tsx
function Page3Child() {
  const title = 'Page3Child'

  return (
    <div style={{ backgroundColor: '#DEB331' }}>
      {/* hogeは宣言されていないのでエラーになる */}
      <h5>{hoge}</h5>
    </div>
  )
}
export default Page3Child

コンポーネントでエラーが発生すると、画面全体が真っ白になります。
コンソールにエラーが出力されています。
React v16 からの機能で、エラーが発生するとコンポーネントツリー全体がアンマンウントされるようになりました。

ErrorBoundaryをインストール

エラーをハンドリングするためのErrorBoundaryを以下のコマンドでインストールします。

npm install react-error-boundary

ErrorBoundaryコンポーネントを作成

子コンポーネントツリーで発生したエラーをハンドリングするためのErrorBoundaryコンポーネントを作成します。
ErrorBoundaryコンポーネントは、クラスコンポーネントで作成し、static getDerivedStateFromError()メソッドとcomponentDidCatch()メソッドを定義します。
static getDerivedStateFromError() はエラーがスローされた後にフォールバック UI をレンダーするために使用します。
componentDidCatch() はエラー情報をログに記録するために使用します。(省略可能です)

components/ErrorBoundary.tsx
import React, { ErrorInfo, ReactNode } from 'react'

type Props = {
  children: ReactNode
}

type State = {
  hasError: boolean
}

class ErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false }
  }

  public static getDerivedStateFromError(error: Error) {
    console.log('getDerivedStateFromError', error)
    return { hasError: true }
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.log('componentDidCatch', error, errorInfo)
    return { hasError: true }
  }

  public render() {
    const { hasError } = this.state
    const { children } = this.props
    return hasError ? <h1>エラーが発生しました</h1> : children
  }
}
export default ErrorBoundary

作成したErrorBoundaryコンポーネントで、Page3コンポーネントのPage3Childコンポーネントを囲みます。

components/Page3.tsx
import ErrorBoundary from './ErrorBoundary '
import Page3Child from './Page3Child'

function Page3() {
  return (
    <div style={{ backgroundColor: '#DFD35F' }}>
      <h3>Page3</h3>
      <ErrorBoundary>
        <Page3Child />
      </ErrorBoundary>
    </div>
  )
}
export default Page3

Page3Childはエラーが発生するようにしておきます。

components/Page3Child.tsx
function Page3Child() {
  const title = 'Page3Child'

  return (
    <div style={{ backgroundColor: '#DEB331' }}>
      {/* hogeは宣言されていないのでエラーになる */}
      <h5>{hoge}</h5>
    </div>
  )
}
export default Page3Child

ErrorBoundaryコンポーネントで囲むと、Page3Childコンポーネント部分に、フォールバックUIが表示されました。

ErrorBoundaryコンポーネントで囲む範囲を変えてみる

ErrorBoundaryコンポーネントで囲む範囲を変えてみます。

Page3コンポーネントで使用していたErrorBoundaryコンポーネントは削除して、AppコンポーネントでPage3コンポーネントをErrorBoundaryコンポーネントで囲んでみます。

components/App.tsx
import ErrorBoundary from './ErrorBoundary '
import Page1 from './Page1'
import Page2 from './Page2'
import Page3 from './Page3'

function App() {
  return (
    <>
      <Page1 />
      <Page2 />
      <ErrorBoundary>
        <Page3 />
      </ErrorBoundary>
    </>
  )
}
export default App

Page3部分にフォールバックUIが表示されました。

次はPage2、Page3コンポーネントをErrorBoundaryコンポーネントで囲んでみます。

components/App.tsx
<Page1 />
<ErrorBoundary>
  <Page2 />
  <Page3 />
</ErrorBoundary>

ErrorBoundaryコンポーネントで囲んでいる部分にフォールバックUIが表示されました。

最後にErrorBoundaryコンポーネントで全てのコンポーネントを囲むようにしてみます。

Apptsx
<ErrorBoundary>
  <Page1 />
  <Page2 />
  <Page3 />
</ErrorBoundary>

ページ全体にフォールバックUIが表示されました。

ErrorBoudaryでキャッチできないエラー

Error Boudaryは、次のようなエラーをキャッチできません。

  • イベントハンドラ内で発生したエラー
  • 非同期処理内で発生したエラー
  • サーバーサイドレンダリングで発生したエラー
  • Error Boudary自体で投げられたエラー (子Error Boudaryではない)

イベントハンドラでエラーを発生させてみる

ためしに、イベントハンドラーでエラーを発生させてみます。
新たにPage4コンポーネントを作成します。

/components/Page4.tsx
function Page4() {
  const onClick = () => {
    throw Error('Page2 throw error') // エラーを発生させる
  }

  return (
    <div style={{ backgroundColor: '#FF7272' }}>
      <h3>Page4</h3>
      <button type="button" onClick={onClick}>
        Button
      </button>
    </div>
  )
}
export default Page4

Page4のButtonをクリックしてみると、コンソールではエラーを確認できますが、画面は何も変わりません。

非同期処理でエラーを発生させてみる

ついでに、非同期処理でエラーを発生させてみます。
Page4コンポーネントはエラーを発生させないように修正し、新たにPage5コンポーネントを作成します。

/components/Page5.tsx
import { useEffect, useState } from 'react'

type UserType = {
  name: string
  age: number
}
const fetchUserAPI = async (): Promise<UserType> => {
  const user = { name: 'yamada', age: 20 }
  throw Error('Error in fetchUserAPI') // エラーを発生させる
  return user
}

function Page5() {
  const [user, setUser] = useState<UserType>()

  useEffect(() => {
    ;(async () => setUser(await fetchUserAPI()))()
  }, [])

  return (
    <div style={{ backgroundColor: '#FF7272' }}>
      <h3>Page5</h3>
      <p>ユーザー名:{user?.name}</p>
    </div>
  )
}
export default Page5

こちらもコンソールではエラーを確認できますが、画面は何も変わりません。

まとめ

Error Boundaryの使用についてはとても簡単でした。
ただイベントハンドラ内で発生するエラーや、非同期処理で発生するエラーを、Reactアプリケーションでは一般的にどのように処理しているのか調べきれませんでした。
次回は、コンポーネントで発生したエラー、イベントハンドラ内でのエラーと、非同期処理で発生したエラーもまとめて処理できる「react-error-boundary」の使用方法について調べてみます。

Discussion