diff --git a/src/App.js b/src/App.js
index 58845d56e429..c69e6da22cb8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,12 +1,15 @@
import React from 'react';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import CustomStatusBar from './components/CustomStatusBar';
+import ErrorBoundary from './components/ErrorBoundary';
import Expensify from './Expensify';
const App = () => (
-
+
+
+
);
diff --git a/src/components/ErrorBoundary/BaseErrorBoundary.js b/src/components/ErrorBoundary/BaseErrorBoundary.js
new file mode 100644
index 000000000000..aee0f94a5665
--- /dev/null
+++ b/src/components/ErrorBoundary/BaseErrorBoundary.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const propTypes = {
+ /* A message posted to `logError` (along with error data) when this component intercepts an error */
+ errorMessage: PropTypes.string.isRequired,
+
+ /* A function to perform the actual logging since different platforms support different tools */
+ logError: PropTypes.func,
+
+ /* Actual content wrapped by this error boundary */
+ children: PropTypes.node.isRequired,
+};
+
+const defaultProps = {
+ logError: () => {},
+};
+
+/**
+ * This component captures an error in the child component tree and logs it to the server
+ * It can be used to wrap the entire app as well as to wrap specific parts for more granularity
+ * @see {@link https://reactjs.org/docs/error-boundaries.html#where-to-place-error-boundaries}
+ */
+class BaseErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {hasError: false};
+ }
+
+ static getDerivedStateFromError() {
+ // Update state so the next render will show the fallback UI.
+ return {hasError: true};
+ }
+
+ componentDidCatch(error, errorInfo) {
+ this.props.logError(this.props.errorMessage, error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ // For the moment we've decided not to render any fallback UI
+ return null;
+ }
+
+ return this.props.children;
+ }
+}
+
+BaseErrorBoundary.propTypes = propTypes;
+BaseErrorBoundary.defaultProps = defaultProps;
+
+export default BaseErrorBoundary;
diff --git a/src/components/ErrorBoundary/index.js b/src/components/ErrorBoundary/index.js
new file mode 100644
index 000000000000..7e8fdfdc2131
--- /dev/null
+++ b/src/components/ErrorBoundary/index.js
@@ -0,0 +1,9 @@
+import BaseErrorBoundary from './BaseErrorBoundary';
+import Log from '../../libs/Log';
+
+BaseErrorBoundary.defaultProps.logError = (errorMessage, error, errorInfo) => {
+ // Log the error to the server
+ Log.alert(errorMessage, 0, {error: error.message, errorInfo}, false);
+};
+
+export default BaseErrorBoundary;
diff --git a/src/components/ErrorBoundary/index.native.js b/src/components/ErrorBoundary/index.native.js
new file mode 100644
index 000000000000..d320b46984e0
--- /dev/null
+++ b/src/components/ErrorBoundary/index.native.js
@@ -0,0 +1,16 @@
+import crashlytics from '@react-native-firebase/crashlytics';
+
+import BaseErrorBoundary from './BaseErrorBoundary';
+import Log from '../../libs/Log';
+
+BaseErrorBoundary.defaultProps.logError = (errorMessage, error, errorInfo) => {
+ // Log the error to the server
+ Log.alert(errorMessage, 0, {error: error.message, errorInfo}, false);
+
+ /* On native we also log the error to crashlytics
+ * Since the error was handled we need to manually tell crashlytics about it */
+ crashlytics().log(`errorInfo: ${JSON.stringify(errorInfo)}`);
+ crashlytics().recordError(error);
+};
+
+export default BaseErrorBoundary;
diff --git a/tests/unit/loginTest.js b/tests/unit/loginTest.js
index c8bcc6c6f00f..615790e4b38a 100644
--- a/tests/unit/loginTest.js
+++ b/tests/unit/loginTest.js
@@ -9,6 +9,13 @@ import React from 'react';
import renderer from 'react-test-renderer';
import App from '../../src/App';
+/* uses and we need to mock the imported crashlytics module
+* due to an error that happens otherwise https://github.com/invertase/react-native-firebase/issues/2475 */
+jest.mock('@react-native-firebase/crashlytics', () => () => ({
+ log: jest.fn(),
+ recordError: jest.fn(),
+}));
+
describe('AppComponent', () => {
it('renders correctly', () => {
renderer.create();