React: アプリ内のエラーを処理する(Error Boundary)
はじめに
今回はReactアプリケーションでのエラー処理方法について調べてみました。
Reactアプリケーションで発生したエラーはError Boundaryで処理することができるようです。
このError Boundaryを使用したエラー処理方法についてまとめてみました。
React:Error Boundary
環境
- Windows11
- vite: v2.7.2
- node: v16.13.2
- react: v17.0.2
- typescript: v4.4.4
エラーが発生していないサンプルプログラムを作成する
まずは エラーが発生していないサンプルプログラムを作成します。
ソースコードを表示
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
import Page1 from './Page1'
import Page2 from './Page2'
import Page3 from './Page3'
function App() {
return (
<>
<Page1 />
<Page2 />
<Page3 />
</>
)
}
export default App
function Page1() {
return (
<div style={{ backgroundColor: '#31C2CF' }}>
<h3>Page1</h3>
</div>
)
}
export default Page1
function Page2() {
return (
<div style={{ backgroundColor: '#D9A0A4' }}>
<h3>Page2</h3>
</div>
)
}
export default Page2
import Page3Child from './Page3Child'
function Page3() {
return (
<div style={{ backgroundColor: '#DFD35F' }}>
<h3>Page3</h3>
<Page3Child />
</div>
)
}
export default Page3
function Page3Child() {
const title = 'Page3Child'
return (
<div style={{ backgroundColor: '#DEB331' }}>
<h5>{title}</h5>
</div>
)
}
export default Page3Child
コンポーネント内でエラーを発生させてみる
次にコンポーネント内でエラーを発生させたときの動作を確認してみます。
エラーが発生するようPage3Childコンポーネントのコードを修正します。
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()
はエラー情報をログに記録するために使用します。(省略可能です)
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コンポーネントを囲みます。
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はエラーが発生するようにしておきます。
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コンポーネントで囲んでみます。
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コンポーネントで囲んでみます。
<Page1 />
<ErrorBoundary>
<Page2 />
<Page3 />
</ErrorBoundary>
ErrorBoundaryコンポーネントで囲んでいる部分にフォールバックUIが表示されました。
最後にErrorBoundaryコンポーネントで全てのコンポーネントを囲むようにしてみます。
<ErrorBoundary>
<Page1 />
<Page2 />
<Page3 />
</ErrorBoundary>
ページ全体にフォールバックUIが表示されました。
ErrorBoudaryでキャッチできないエラー
Error Boudaryは、次のようなエラーをキャッチできません。
- イベントハンドラ内で発生したエラー
- 非同期処理内で発生したエラー
- サーバーサイドレンダリングで発生したエラー
- Error Boudary自体で投げられたエラー (子Error Boudaryではない)
イベントハンドラでエラーを発生させてみる
ためしに、イベントハンドラーでエラーを発生させてみます。
新たにPage4コンポーネントを作成します。
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コンポーネントを作成します。
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