この記事は、ニフティグループ Advent Calendar 2024 16日目の記事です。
はじめに
今月は二本目のブログ投稿をする宮本です。ひと月の間にブログ二本書くのはなかなかしんどいですね。一人アドベントカレンダーをやっている方とか凄いと思います。
今回は、ReactのErrorBoundaryのログ出力についてです。なお、React単体で実装できるクラスコンポーネントを利用するものではなく、関数コンポーネントで利用できるreact-error-boundary を対象としています。
ログを簡単に出したい。あとついでに使いまわせるようにしたい。
平たく言えばReactコンポーネントのtry-catchのような役割をするErrorBoundaryですが、実施できることは結構限られます。
- エラー発生時の代替コンポーネントの表示
- 代替コンポーネント内でのリセット処理の追加
- エラー発生時にあらかじめ指定したログ出力用の関数を実行
他にも明示的にErrorBoundaryの呼び出し・再レンダリングができるhookが用意されていたりしますが、応用になるため今回は割愛。とりあえずErrorBoundaryを入れよう!となった場合に使うのは、代替コンポーネントの表示とエラー時のログ表示だと思います。
さて、代替コンポーネントの中身自体はアプリケーションによりまちまちだと思いますが、エラーログの表示となると表示内容は割と限られます。エラーの種類ごとにエラー内容を変えるとなると細かく分岐を入れる必要がありますが、「最低限エラーがわかれば……」というレベルならできるだけ楽に書きたいです。
ErrorBoundaryでログ出力をする例として、onErrorで実行する関数内でログ出力をする方法が公式で紹介されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// https://github.com/bvaughn/react-error-boundary?tab=readme-ov-file#logging-errors-with-onerror より一部修正 "use client"; import { ErrorInfo } from 'react'; import { ErrorBoundary } from "react-error-boundary"; // 変更点)infoの型はreact-error-boundary 4.0からReactのErrorInfo型を利用 const logError = (error: Error, info: ErrorInfo) => { // Do something with the error, e.g. log to an external API }; const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplication /> </ErrorBoundary> ); |
しかしこのonErrorで実行する関数、引数があらかじめ決まっているエラーオブジェクトとコンポーネントのスタックしか渡されません。一応スタックは渡されるのでどこで発生したエラーかはわかるようになっています。が、せめてErrorBoundaryを仕掛ける場所が決まっているのであれば、ログ中に一発で「エラー発生箇所はここだ!」とわかるような文言を仕込みたいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// こんな感じにErrorBoundaryがたくさんあると、いざエラーが発生した場合もどこで発生したエラーなのか分かりづらい const logError = (error: Error, info: { componentStack: string }) => { console.error("エラー発生!", Error, componentStack) }; const uiFirst = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplicationFirst /> </ErrorBoundary> ); const uiSecond = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplicationSecond /> </ErrorBoundary> ); |
そこで、次のようにログ出力用の関数を作成するcreateLogError関数を用意して、createLogErrorの引数で与えた値(logId)をログ出力で利用するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const createLogError = (logId: string) => (error: Error, info: ErrorInfo): void => { console.error(`${logId} エラー発生!`, error, info.componentStack); }; const uiFirst = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={createLogError("ui-first")}> <ExampleApplicationFirst /> </ErrorBoundary> ); const uiSecond = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={createLogError("ui-second")}> <ExampleApplicationSecond /> </ErrorBoundary> ); |
上記の例を実際に使うと以下のようになります。
1 2 3 4 5 6 7 8 |
export default function Home() { return ( <ErrorBoundary FallbackComponent={FallbackComponent} onError={createLogError('ErrorItem')}> {/* 内部で `throw new Error('なんか凄いエラー');` を実行するコンポーネント */} <ErrorItem /> </ErrorBoundary> ); } |
上記の例ではコンポーネント名をそのままIDに入れていますが、機能に即した名前が入っているともっとわかりやすくなると思います。ゆるくログ出力するだけであれば、これで十二分ですね。
おわりに
今回はErrorBoundaryを利用したログ出力について紹介しました。Reactのコンポーネントで発生したエラーは、止める場所がなければどこまでも遡り最終的にページ全体が落ちてしまいます。最低限エラーが発生しそうな箇所ではErrorBoundaryを設定しておき、余裕があればログ出力できるようにしておくと調査がスムーズですね。
また、そもそもエラーが発生したコンポーネントを再レンダリングするトリガーをFallbackComponent内に用意することもできるので、エラー処理といえどもなかなか奥が深いです。
明日は、kiwi26さんの記事です。
お楽しみに!