diff --git a/native/crash.react.js b/native/crash.react.js index 0da524bc2..cb67c17f5 100644 --- a/native/crash.react.js +++ b/native/crash.react.js @@ -1,268 +1,266 @@ // @flow import Clipboard from '@react-native-community/clipboard'; import invariant from 'invariant'; import _shuffle from 'lodash/fp/shuffle'; -import PropTypes from 'prop-types'; import * as React from 'react'; import { View, Text, Platform, StyleSheet, ScrollView, ActivityIndicator, } from 'react-native'; import ExitApp from 'react-native-exit-app'; import Icon from 'react-native-vector-icons/FontAwesome'; import { sendReportActionTypes, sendReport } from 'lib/actions/report-actions'; import { logOutActionTypes, logOut } from 'lib/actions/user-actions'; import { preRequestUserStateSelector } from 'lib/selectors/account-selectors'; import type { LogOutResult } from 'lib/types/account-types'; import type { ErrorData } from 'lib/types/report-types'; import { type ClientReportCreationRequest, type ReportCreationResponse, reportTypes, } from 'lib/types/report-types'; -import { - type PreRequestUserState, - preRequestUserStatePropType, -} from 'lib/types/session-types'; +import type { PreRequestUserState } from 'lib/types/session-types'; import { actionLogger } from 'lib/utils/action-logger'; -import type { DispatchActionPromise } from 'lib/utils/action-utils'; -import { connect } from 'lib/utils/redux-utils'; +import { + type DispatchActionPromise, + useServerCall, + useDispatchActionPromise, +} from 'lib/utils/action-utils'; import { sanitizeAction, sanitizeState } from 'lib/utils/sanitization'; import sleep from 'lib/utils/sleep'; import Button from './components/button.react'; import ConnectedStatusBar from './connected-status-bar.react'; import { persistConfig, codeVersion } from './redux/persist'; -import type { AppState } from './redux/redux-setup'; +import { useSelector } from './redux/redux-utils'; import { wipeAndExit } from './utils/crash-utils'; const errorTitles = ['Oh no!!', 'Womp womp womp...']; -type Props = { - errorData: $ReadOnlyArray, +type BaseProps = {| + +errorData: $ReadOnlyArray, +|}; +type Props = {| + ...BaseProps, // Redux state - preRequestUserState: PreRequestUserState, + +preRequestUserState: PreRequestUserState, // Redux dispatch functions - dispatchActionPromise: DispatchActionPromise, + +dispatchActionPromise: DispatchActionPromise, // async functions that hit server APIs - sendReport: ( + +sendReport: ( request: ClientReportCreationRequest, ) => Promise, - logOut: (preRequestUserState: PreRequestUserState) => Promise, -}; + +logOut: (preRequestUserState: PreRequestUserState) => Promise, +|}; type State = {| - errorReportID: ?string, - doneWaiting: boolean, + +errorReportID: ?string, + +doneWaiting: boolean, |}; class Crash extends React.PureComponent { - static propTypes = { - errorData: PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.object.isRequired, - info: PropTypes.shape({ - componentStack: PropTypes.string.isRequired, - }), - }), - ).isRequired, - preRequestUserState: preRequestUserStatePropType.isRequired, - dispatchActionPromise: PropTypes.func.isRequired, - sendReport: PropTypes.func.isRequired, - logOut: PropTypes.func.isRequired, - }; errorTitle = _shuffle(errorTitles)[0]; state: State = { errorReportID: null, doneWaiting: false, }; componentDidMount() { this.props.dispatchActionPromise(sendReportActionTypes, this.sendReport()); this.timeOut(); } async timeOut() { // If it takes more than 10s, give up and let the user exit await sleep(10000); this.setState({ doneWaiting: true }); } render() { const errorText = [...this.props.errorData] .reverse() .map((errorData) => errorData.error.message) .join('\n'); let crashID; if (this.state.errorReportID) { crashID = ( {this.state.errorReportID} ); } else { crashID = ; } const buttonStyle = { opacity: Number(this.state.doneWaiting) }; return ( {this.errorTitle} I'm sorry, but the app crashed. Crash report ID: {crashID} Here's some text that's probably not helpful: {errorText} ); } async sendReport() { const result = await this.props.sendReport({ type: reportTypes.ERROR, platformDetails: { platform: Platform.OS, codeVersion, stateVersion: persistConfig.version, }, errors: this.props.errorData.map((data) => ({ errorMessage: data.error.message, stack: data.error.stack, componentStack: data.info && data.info.componentStack, })), preloadedState: sanitizeState(actionLogger.preloadedState), currentState: sanitizeState(actionLogger.currentState), actions: actionLogger.actions.map(sanitizeAction), }); this.setState({ errorReportID: result.id, doneWaiting: true, }); } onPressKill = () => { if (!this.state.doneWaiting) { return; } ExitApp.exitApp(); }; onPressWipe = async () => { if (!this.state.doneWaiting) { return; } this.props.dispatchActionPromise(logOutActionTypes, this.logOutAndExit()); }; async logOutAndExit() { try { await this.props.logOut(this.props.preRequestUserState); } catch (e) {} await wipeAndExit(); } onCopyCrashReportID = () => { invariant(this.state.errorReportID, 'should be set'); Clipboard.setString(this.state.errorReportID); }; } const styles = StyleSheet.create({ button: { backgroundColor: '#FF0000', borderRadius: 5, marginHorizontal: 10, paddingHorizontal: 10, paddingVertical: 5, }, buttonText: { color: 'white', fontSize: 16, }, buttons: { flexDirection: 'row', }, container: { alignItems: 'center', backgroundColor: 'white', flex: 1, justifyContent: 'center', }, copyCrashReportIDButtonText: { color: '#036AFF', }, crashID: { flexDirection: 'row', paddingBottom: 12, paddingTop: 2, }, crashIDText: { color: 'black', paddingRight: 8, }, errorReportID: { flexDirection: 'row', height: 20, }, errorReportIDText: { color: 'black', paddingRight: 8, }, errorText: { color: 'black', fontFamily: Platform.select({ ios: 'Menlo', default: 'monospace', }), }, header: { color: 'black', fontSize: 24, paddingBottom: 24, }, scrollView: { flex: 1, marginBottom: 24, marginTop: 12, maxHeight: 200, paddingHorizontal: 50, }, text: { color: 'black', paddingBottom: 12, }, }); -export default connect( - (state: AppState) => ({ - preRequestUserState: preRequestUserStateSelector(state), - }), - { sendReport, logOut }, -)(Crash); +export default React.memo(function ConnectedCrash(props: BaseProps) { + const preRequestUserState = useSelector(preRequestUserStateSelector); + + const dispatchActionPromise = useDispatchActionPromise(); + const callSendReport = useServerCall(sendReport); + const callLogOut = useServerCall(logOut); + return ( + + ); +});