diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8499cd14..012b23ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#103](https://github.com/shlinkio/shlink-web-client/issues/103) Fixed visits page getting freezed when loading large amounts of visits.
* [#111](https://github.com/shlinkio/shlink-web-client/issues/111) Fixed crash when trying to load a map modal with only one location.
+* [#115](https://github.com/shlinkio/shlink-web-client/issues/115) Created `ErrorHandler` component which will prevent crashes in app to make it unusable.
## 2.0.1 - 2019-03-03
diff --git a/src/common/ErrorHandler.js b/src/common/ErrorHandler.js
new file mode 100644
index 00000000..fb641a79
--- /dev/null
+++ b/src/common/ErrorHandler.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import * as PropTypes from 'prop-types';
+import './ErrorHandler.scss';
+import { Button } from 'reactstrap';
+
+const ErrorHandler = ({ location }) => class ErrorHandler extends React.Component {
+ static propTypes = {
+ children: PropTypes.node.isRequired,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true };
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
Oops! This is awkward :S
+
It seems that something went wrong. Try refreshing the page or just click this button.
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+};
+
+export default ErrorHandler;
diff --git a/src/common/ErrorHandler.scss b/src/common/ErrorHandler.scss
new file mode 100644
index 00000000..0c757135
--- /dev/null
+++ b/src/common/ErrorHandler.scss
@@ -0,0 +1,9 @@
+@import '../utils/mixins/vertical-align.scss';
+
+.error-handler {
+ @include vertical-align();
+
+ padding: 20px;
+ text-align: center;
+ width: 100%;
+}
diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.js
index eb7e0c9d..6fcc775b 100644
--- a/src/common/services/provideServices.js
+++ b/src/common/services/provideServices.js
@@ -3,6 +3,7 @@ import MainHeader from '../MainHeader';
import Home from '../Home';
import MenuLayout from '../MenuLayout';
import AsideMenu from '../AsideMenu';
+import ErrorHandler from '../ErrorHandler';
const provideServices = (bottle, connect, withRouter) => {
bottle.constant('window', global.window);
@@ -29,6 +30,8 @@ const provideServices = (bottle, connect, withRouter) => {
bottle.decorator('MenuLayout', withRouter);
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
+
+ bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window');
};
export default provideServices;
diff --git a/src/index.js b/src/index.js
index c875ae06..5b6446f9 100644
--- a/src/index.js
+++ b/src/index.js
@@ -16,14 +16,16 @@ import './index.scss';
// This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS
fixLeafletIcons();
-const { App, ScrollToTop } = container;
+const { App, ScrollToTop, ErrorHandler } = container;
render(
-
-
-
+
+
+
+
+
,
document.getElementById('root')
diff --git a/test/common/ErrorHandler.test.js b/test/common/ErrorHandler.test.js
new file mode 100644
index 00000000..58325a65
--- /dev/null
+++ b/test/common/ErrorHandler.test.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { Button } from 'reactstrap';
+import createErrorHandler from '../../src/common/ErrorHandler';
+
+describe('', () => {
+ const window = {
+ location: {
+ reload: jest.fn(),
+ },
+ };
+ let wrapper;
+
+ beforeEach(() => {
+ const ErrorHandler = createErrorHandler(window);
+
+ wrapper = shallow(Foo} />);
+ });
+
+ afterEach(() => wrapper.unmount());
+
+ it('renders children when no error has occurred', () => {
+ expect(wrapper.text()).toEqual('Foo');
+ expect(wrapper.find(Button)).toHaveLength(0);
+ });
+
+ it('renders error page when error has occurred', () => {
+ wrapper.setState({ hasError: true });
+
+ expect(wrapper.text()).toContain('Oops! This is awkward :S');
+ expect(wrapper.text()).toContain(
+ 'It seems that something went wrong. Try refreshing the page or just click this button.'
+ );
+ expect(wrapper.find(Button)).toHaveLength(1);
+ });
+});