17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React 16のError Boundaryの運用方法

Last updated at Posted at 2018-12-07

React 16のUncaught Errorに対策して、ErrorBoudaryおよびErrorBoudaryHOC(HigherOrderComponent)を作成する

Uncaught Error時のReactの挙動

React 15: エラーが発生しても、UIが変わらない
react15.gif
React 16 以降: コンポーネントツリーが全部unmountされる

As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree.
react16.gif

React 16のErrorBoudaryの対応

ErrorBoudaryを作成して、componentDidCatchを使う

ErrorBoudary

  • 子孫コンポーネントの中にエラーが発生したら、catchできる
  • componentDidCatchというfunctionを使って、発生したエラーをハンドリングできる
  • ErrorBoudaryはいくつでも利用できる

componentDidCatch(error, errorInfo)

  • JavaScript catchみたい
  • error: react error
  • errorInfo: コンポーネントのエラーが発生したところがわかりやすい
The above error occurred in the <TodoItem> component:
    in TodoItem (created by TodoApp)
    in div (created by TodoList)
    in TodoList (created by TodoApp)
    in ErrorBoundary (created by TodoApp)
    in div (created by TodoApp)
    in TodoApp

React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary.

Code Demo jsfiddle

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {error: null, errorInfo: null};
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({error, errorInfo})
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{whiteSpace: 'pre-wrap'}}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

ErrorBoundaryを使用する

<ErrorBoundary>
 <TodoList/>
 <AddTodo/>
</ErrorBoundary>

エラーハンドリングのカスタマイズ jsfiddle

PropとしてonError(発生したエラーをハンドリングするfuntion)とErrorComponent(Error message)を渡します。

const ErrorComponent = ({error, errorInfo}) => (
  <div>
    <h2>Something went wrong.</h2>
    <details style={{whiteSpace: 'pre-wrap'}}>
      {error && error.toString()}
      <br />
      {errorInfo.componentStack}
    </details>
  </div>
)

const onError = (error, errorInfo) => (
  console.error(error, errorInfo)
)

<ErrorBoundary ErrorComponent={ErrorComponent} onError={onError}>
...
</ErrorBoundary>

Higher Order Component としてErrorBoudaryを利用する

コンポネートごとにErrorBoudaryを使うと、ある時が面倒です。

<ErrorBoundary>
 <TodoList>
  <ErrorBoundary>
    <TodoItem/>
  </ErrorBoundary>
  <ErrorBoundary>
    <TodoItem/>
  </ErrorBoundary>
 </TodoList>
 <AddTodo/>
</ErrorBoundary>

-> Higher Order Componentを利用する

class ErrorBoundary extends React.Component {
...
}

function ErrorBoundaryHOC(Component){
    return class ErrorBoundaryComponent extends React.Component {
    render(){
      return (
        <ErrorBoundary>
          <Component {...this.props} />
        </ErrorBoundary>
      )
    }
  }
}

上記の問題は解決できます。また、HOCを使うと、view側は何も変更しないで、ErrorBoundaryも適用できる。

const TodoItemHOC = ErrorBoundaryHOCTodoItem
//Viewを変更したくない
//const TodoItem = ErrorBoundaryHOC(require('./TodoItem')

<ErrorBoundary>
 <TodoList>
  <TodoItemHOC/>
  <TodoItemHOC/>
 </TodoList>
 <AddTodo/>
</ErrorBoundary>

##お負け

React 15みたいにUIが変わりたくない

前述によると、エラーが発生したら、ErrorComponentをレンダリングですが、多分時々そこをやりたくないと思います。Userにとってもエラーメッセージを見たくないな。

そこを実装するために、単純にエラーが発生する時、ErrorComponentをレンダリングしないで、childrenをレンダリングします。

  render() {
    // Normally, just render children
    return this.props.children;
  }  

エラーハンドリングがデフォルトにして、全プロジェクトを使う

ErrorBoundaryを作成する

ErrorBoundary.defaults = {
  ErrorComponent: null,
  onError: null
}

ErrorBoundary.setOptions = function(opt) {
  merge(ErrorBoundary.defaults, opt)
}

class ErrorBoundary extends React.Component {
   constructor(props) {
    super(props);
    const {onError, ErrorComponent} = this.props
    //propsが優先される
    const opt = merge({}, ErrorBoundary.defaults, {onError, ErrorComponent}}
    this.state = merge({error: null, errorInfo: null}, opt}
  }
}

ErrorBoundaryを利用する

import ErrorBoundary from './ErrorBoundary'

ErrorBoundary.setOptions({onError, ErrorComponent})
17
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?