こんにちは。スタメン エンジニアのミツモトです。 スタメンで開発しているサービス TUNAG では、JSのフレームワークとして部分的にReactを採用しています。 最近、Reactのバージョンアップが行われ、それに伴いエラー対応を行ったので、 今回はその事についてご紹介させていただきます。
目次
はじめに
Reactのバージョンをv15.4.2 → v16.8.6 に上げたことで、エラーが発生した場合の表示が変わりました。 v15では、描画されたコンポーネントはエラーが発生しても画面が変わらなかったのが、v16だとコンポーネントのツリー全体がアンマウントされる(表示されなくなる)ようになりました。
壊れたコンポーネントがアンマウントされるのは良いのですが、このままだと、サービスを利用するユーザーにとっては、「何が起きたんだ?」となり、UXとして良くありません。そこで登場するのがErrorBoundaryです。
ErrorBoundary
自身の子コンポーネントツリーで発生した JavaScript エラーをキャッチし、エラーを記録し、クラッシュしたコンポーネントツリーの代わりにフォールバック用の UI を表示する React コンポーネント
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service logErrorToMyService(error, errorInfo); } render() { if (this.state.hasError) { // You can render any custom fallback UI return ( <div> アプリケーションエラーにより表示できません。時間を置いて、再度更新してください。 </div> ) } return this.props.children; } } // エラーハンドリングしたい他のコンポーネントを囲う <ErrorBoundary> <MyWidget /> </ErrorBoundary>
getDerivedStateFromError と componentDidCatch は、コンポーネントのレンダー中、ライフサイクルメソッド内、コンストラクタ内でエラーが発生したときに呼び出されます。
上記のコードでは、getDerivedStateFromError
により、 hasError の state を切り替えて代わりとなるコンポーネントを表示し、 componentDidCatch によってエラー情報を記録しています。 囲ったコンポーネントだけでなく、配下のコンポーネントのエラーもキャッチします。
ErrorBoundary を使うと、以下のように代わりのコンポーネントが表示されます。
ErrorBoundary × Bugsnag
当初は、公式のようにErrorBoundaryを実装していました。スタメンでは、TUNAGのエラー監視に Bugsnag というサービスを採用しているのですが、Bugsnag が ErrorBoundary を扱える パッケージ を提供していることを知り、エラーの通知含めシンプルに実装できるため、そちらに変更しました。
必要なパッケージ
- dotenv
- @bugsnag/js
- @bugsnag/plugin-react
実装
まずは、 Bugsnag のAPIキーを環境変数として定義します。
// .env
BUGSNAG_API_KEY=*****************************(取得したBugsnagのAPIキーを入力)
webpack の DefinePlugin により、 process.env.BUGSNAG_API_KEY で、環境変数のAPIキーを呼び出せるようにします。
// webpack.config.js // 抜粋 const webpack = require('webpack'); require('dotenv').config(); <200b> module.exports = () => { return ({ plugins: [ new webpack.DefinePlugin({ 'process.env': { 'BUGSNAG_API_KEY': JSON.stringify(process.env.BUGSNAG_API_KEY), } }), ], }); };
bugsnagClient、ErrorBoundary、エラー発生時に代わりに表示するコンポーネント( DefaultFallbackComponent )を用意します
// src/utilities/bugsnag/bugsnagClient.js import bugsnag from '@bugsnag/js'; const bugsnagClient = bugsnag(`${process.env.BUGSNAG_API_KEY}`); export default bugsnagClient;
// src/utilities/ErrorBoundary.js import React from 'react'; import bugsnagReact from '@bugsnag/plugin-react'; import bugsnagClient from 'utilities/bugsnag/bugsnagClient'; bugsnagClient.use(bugsnagReact, React); const ErrorBoundary = bugsnagClient.getPlugin('react'); export default ErrorBoundary; export const DefaultFallbackComponent = () => { return ( <div> アプリケーションエラーにより表示できません。時間を置いて、再度更新してください。 </div> ); };
最後に、エラーをキャッチしたいコンポーネントを囲います。その際、 ErrorBoundary の FallbackComponent プロパティに、代わりに表示するコンポーネントを渡します。
import ErrorBoundary, { DefaultFallbackComponent } from 'src/utilities/ErrorBoundary'; <ErrorBoundary FallbackComponent={DefaultFallbackComponent} > <MyComponent /> </ErrorBoundary>
以上で、ユーザーに対して適切なエラー画面を表示でき、同時にエラーを通知できます。
おまけ(Bugsnagのエラー通知にユーザー情報を付与)
bugsnagClientにユーザー情報を渡すことで、Bugsnagのエラー通知にユーザー情報を含められます。
bugsnagClient.user = { companyId: 1, userId: 1, name: 'テスト 太郎', };
Bugsnagの画面
4. おわりに
ErrorBoundary × Bugsnag による React のエラー対応についてご紹介しました。 React をバージョンアップしたことで、新たな APIやライブラリを使うことができるようになったので、これから新機能を開発するのが楽しみです。
スタメンではフロントエンド含め、エンジニアを募集しています。 興味のある方は是非ご覧ください!