Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NO QA]Feature: Critical Error Handling #3527

Merged
merged 8 commits into from
Jun 11, 2021
6 changes: 5 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react';
import {SafeAreaProvider} from 'react-native-safe-area-context';

kidroca marked this conversation as resolved.
Show resolved Hide resolved
import CustomStatusBar from './components/CustomStatusBar';
import Expensify from './Expensify';
import ErrorBoundary from './components/ErrorBoundary';

const App = () => (
<SafeAreaProvider>
<CustomStatusBar />
<Expensify />
<ErrorBoundary errorMessage="[App] A Crash was intercepted by the main error boundary">
kidroca marked this conversation as resolved.
Show resolved Hide resolved
<Expensify />
</ErrorBoundary>
</SafeAreaProvider>
);

Expand Down
52 changes: 52 additions & 0 deletions src/components/ErrorBoundary/BaseErrorBoundary.js
Original file line number Diff line number Diff line change
@@ -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;
9 changes: 9 additions & 0 deletions src/components/ErrorBoundary/index.js
Original file line number Diff line number Diff line change
@@ -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;
16 changes: 16 additions & 0 deletions src/components/ErrorBoundary/index.native.js
Original file line number Diff line number Diff line change
@@ -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: ${errorInfo}`);
kidroca marked this conversation as resolved.
Show resolved Hide resolved
crashlytics().recordError(error);
};

export default BaseErrorBoundary;
5 changes: 5 additions & 0 deletions tests/unit/loginTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import React from 'react';
import renderer from 'react-test-renderer';
import App from '../../src/App';

jest.mock('@react-native-firebase/crashlytics', () => ({
log: jest.fn(),
recordError: jest.fn(),
}));

kidroca marked this conversation as resolved.
Show resolved Hide resolved
describe('AppComponent', () => {
it('renders correctly', () => {
renderer.create(<App />);
Expand Down