React 16のError Boundaryの運用方法

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

React 15: エラーが発生しても、UIが変わらない
![react15.gif](https://i.gyazo.com/62c29fc758c5ddb5e1b88e32561f91ad.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](https://i.gyazo.com/5d729e7f121f0a5ea4f3b284ddf73e95.gif)


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

#### componentDidCatch(error, errorInfo)
- JavaScript `catch`みたい
- error: react error
- errorInfo: コンポーネントのエラーが発生したところがわかりやすい

```xml
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](https://jsfiddle.net/DinhDuyThanh/8jsq0425)
```javascript
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を使用する

```xml
<ErrorBoundary>
 <TodoList/>
 <AddTodo/>
</ErrorBoundary>
```

###  エラーハンドリングのカスタマイズ [jsfiddle](https://jsfiddle.net/DinhDuyThanh/0kcwbqam)
Propとして`onError`(発生したエラーをハンドリングするfuntion)と`ErrorComponent`(Error message)を渡します。

```javascript
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を使うと、ある時が面倒です。

```jsx
<ErrorBoundary>
 <TodoList>
  <ErrorBoundary>
    <TodoItem/>
  </ErrorBoundary>
  <ErrorBoundary>
    <TodoItem/>
  </ErrorBoundary>
 </TodoList>
 <AddTodo/>
</ErrorBoundary>

```

-> Higher Order Componentを利用する

```javascript
class ErrorBoundary extends React.Component {
...
}

function ErrorBoundaryHOC(Component){
　　　　return class ErrorBoundaryComponent extends React.Component {
    render(){
      return (
        <ErrorBoundary>
          <Component {...this.props} />
        </ErrorBoundary>
      )
    }
  }
}
```
上記の問題は解決できます。また、HOCを使うと、view側は何も変更しないで、ErrorBoundaryも適用できる。

```jsx
const TodoItemHOC = ErrorBoundaryHOC（TodoItem）
//Viewを変更したくない
//const TodoItem = ErrorBoundaryHOC（require('./TodoItem'）

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

```


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

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

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

```

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

```javascript
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を利用する

```javascript
import ErrorBoundary from './ErrorBoundary'

ErrorBoundary.setOptions({onError, ErrorComponent})
```