ErrorBoundary.jsx 3.01 KB
Newer Older
1
2
import React from "react";
import PropTypes from "prop-types";
3
import { setDisplayName } from "recompose";
4
import compose from "recompose/compose";
5
6
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
7
import RequestParams from "../../redux/api/RequestParams";
8
import getActions from "../../redux/api/getActions";
9
import APP_ROUTES from "../../config/appRoutes";
Florent Chehab's avatar
Florent Chehab committed
10
import AlertService from "../../services/AlertService";
11
12

function clear() {
13
14
  return {
    error: null,
Florent Chehab's avatar
Florent Chehab committed
15
    errorInfo: null
16
  };
17
18
19
20
21
22
23
24
25
26
27
28
}

/**
 * Component to act as an error boundary, to prevent the app from deep unrecoverable crashes.
 */
class ErrorBoundary extends React.Component {
  state = clear();

  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error,
Florent Chehab's avatar
Florent Chehab committed
29
      errorInfo
30
    });
31

32
    const data = "stack" in error ? { componentStack: error.stack } : errorInfo;
33

34
    const params = RequestParams.Builder.withData(data).build();
35
    this.props.logErrorOnServer(params);
36
37
38
  }

  render() {
39
    const { error, errorInfo } = this.state;
40
41
42
43
    // In case of error
    if (errorInfo) {
      // eslint-disable-next-line no-console
      console.log(error, errorInfo);
Florent Chehab's avatar
Florent Chehab committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
      AlertService.open({
        info: false,
        title:
          "Une erreur inconnue c'est produite dans l'application. Nous vous prions de nous en excuser.",
        description:
          "Nous vous invitons à recharger la page. Si l'erreur persiste, merci de contacter les administrateurs du site; l'erreur leur a été transmise.",
        agreeText: "C'est noté, je sais que vous faîtes de votre mieux :)",
        disagreeText: "Retourner à l'accueil",

        handleResponse: agreed => {
          this.setState(clear());
          // May need to click twice, but there seem to be no other ways
          if (!agreed) this.props.history.push(APP_ROUTES.base);
        }
      });

      return <></>;
61
62
63
64
65
66
67
68
69
    }

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

ErrorBoundary.propTypes = {
  children: PropTypes.node.isRequired,
70
  logErrorOnServer: PropTypes.func.isRequired,
71
  history: PropTypes.object.isRequired
72
73
};

74
75
76
const mapDispatchToProps = dispatch => ({
  logErrorOnServer: params =>
    dispatch(getActions("frontendErrors").create(params))
77
78
79
});

const ConnectedErrorBoundary = compose(
80
81
82
83
  connect(
    () => ({}),
    mapDispatchToProps
  ),
84
  withRouter
85
86
)(ErrorBoundary);

87
88
89
90
91
/**
 * HOC (higher order component) wrapper to provide an error boundary to the sub components.
 *
 * @returns {function(*): function(*): *}
 */
92
// eslint-disable-next-line import/prefer-default-export
93
export function withErrorBoundary() {
94
95
96
97
98
99
100
101
102
103
  return Component =>
    setDisplayName("error-boundary")(
      // We need to forward the ref otherwise the styles are not correctly applied.
      // eslint-disable-next-line react/display-name
      React.forwardRef((props, ref) => (
        <ConnectedErrorBoundary>
          <Component {...props} ref={ref} />
        </ConnectedErrorBoundary>
      ))
    );
104
}