import { Component } from 'react';
import PropTypes from 'prop-types';

/**
 * Helper function to evaluate when the boundary's reset keys have changed. It
 * uses a strict equality check, so objects, arrays, and functions need to be
 * appropriately memoized.
 */
const changedArray = (a = [], b = []) => {
  if (a.length !== b.length) return true;
  return a.some((el, i) => el !== b[i]);
};
const initialState = {
  error: null
};
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = {
      ...initialState
    };
    this.resetErrorBoundary = this.resetErrorBoundary.bind(this);
  }
  static getDerivedStateFromError(error) {
    return {
      error
    };
  }
  componentDidUpdate(prevProps) {
    const {
      error
    } = this.state;
    const {
      resetKeys
    } = this.props;
    if (error && changedArray(prevProps.resetKeys, resetKeys)) {
      this.reset();
    }
  }
  componentDidCatch(error, errorInfo) {
    const {
      onError
    } = this.props;
    if (onError) onError(error, errorInfo);
  }
  resetErrorBoundary(...args) {
    this.reset(...args);
  }
  reset(...args) {
    const {
      onReset
    } = this.props;
    if (onReset) onReset(...args);
    this.setState({
      ...initialState
    });
  }
  render() {
    const {
      error
    } = this.state;
    const {
      fallback,
      children
    } = this.props;
    if (error) {
      if (typeof fallback === 'function') {
        const props = {
          error,
          resetErrorBoundary: this.resetErrorBoundary
        };
        const C = fallback;
        return <C {...props} />;
      }
      return fallback;
    }
    return children;
  }
}
ErrorBoundary.defaultProps = {
  onError: () => {},
  onReset: () => {},
  resetKeys: [],
  fallback: null,
  children: null
};
ErrorBoundary.propTypes = {
  onError: PropTypes.func,
  onReset: PropTypes.func,
  resetKeys: PropTypes.array,
  // eslint-disable-line react/forbid-prop-types
  fallback: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node), PropTypes.elementType]),
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)])
};
export default ErrorBoundary;