diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 9005374826b3..d30bb45bdb31 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -19,6 +19,7 @@ import * as Policy from '../../../libs/actions/Policy'; import CONST from '../../../CONST'; import ROUTES from '../../../ROUTES'; import ONYXKEYS from '../../../ONYXKEYS'; +import usePrevious from '../../../hooks/usePrevious'; import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes'; import {withNetwork} from '../../../components/OnyxProvider'; import networkPropTypes from '../../../components/networkPropTypes'; @@ -64,140 +65,142 @@ const defaultProps = { reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, }; -class WorkspaceReimburseView extends React.Component { - constructor(props) { - super(props); - this.state = { - currentRatePerUnit: this.getCurrentRatePerUnitLabel(), - }; - } - - componentDidMount() { - this.fetchData(); - } - - componentDidUpdate(prevProps) { - if (prevProps.policy.customUnits !== this.props.policy.customUnits || prevProps.preferredLocale !== this.props.preferredLocale) { - this.setState({currentRatePerUnit: this.getCurrentRatePerUnitLabel()}); - } - - const reconnecting = prevProps.network.isOffline && !this.props.network.isOffline; - if (!reconnecting) { - return; - } +function WorkspaceReimburseView(props) { + const [currentRatePerUnit, setCurrentRatePerUnit] = useState(''); + const prevIsOffline = usePrevious(props.network.isOffline); + const viewAllReceiptsUrl = `expenses?policyIDList=${props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; + const distanceCustomUnit = _.find(lodashGet(props.policy, 'customUnits', {}), (unit) => unit.name === 'Distance'); + const distanceCustomRate = _.find(lodashGet(props.distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate'); + + const getNumericValue = useCallback( + (value) => { + const propsToLocaleDigit = props.toLocaleDigit; + const numValue = parseFloat(value.toString().replace(propsToLocaleDigit('.'), '.')); + if (Number.isNaN(numValue)) { + return NaN; + } + return numValue.toFixed(3); + }, + [props.toLocaleDigit], + ); + + const getRateDisplayValue = useCallback( + (value) => { + const propsToLocaleDigit = props.toLocaleDigit; + const numValue = getNumericValue(value); + if (Number.isNaN(numValue)) { + return ''; + } + return numValue.toString().replace('.', propsToLocaleDigit('.')).substring(0, value.length); + }, + [getNumericValue, props.toLocaleDigit], + ); - this.fetchData(); - } + const getRateLabel = useCallback((customUnitRate) => getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET), [getRateDisplayValue]); - getCurrentRatePerUnitLabel() { - const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), (unit) => unit.name === 'Distance'); - const customUnitRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate'); - const currentUnit = this.getUnitLabel(lodashGet(distanceCustomUnit, 'attributes.unit', 'mi')); - const currentRate = this.getRateLabel(customUnitRate); - const perWord = this.props.translate('common.per'); + const getUnitLabel = useCallback( + (value) => { + const propsTranslate = props.translate; + return propsTranslate(`common.${value}`); + }, + [props.translate], + ); + + const getCurrentRatePerUnitLabel = useCallback(() => { + const propsTranslate = props.translate; + const customUnitRate = _.find(distanceCustomUnit && distanceCustomUnit.rates, (rate) => rate && rate.name === 'Default Rate'); + const currentUnit = getUnitLabel((distanceCustomUnit && distanceCustomUnit.attributes && distanceCustomUnit.attributes.unit) || 'mi'); + const currentRate = getRateLabel(customUnitRate); + const perWord = propsTranslate('common.per'); return `${currentRate} ${perWord} ${currentUnit}`; - } - - getRateLabel(customUnitRate) { - return this.getRateDisplayValue(lodashGet(customUnitRate, 'rate', 0) / CONST.POLICY.CUSTOM_UNIT_RATE_BASE_OFFSET); - } - - getUnitLabel(value) { - return this.props.translate(`common.${value}`); - } - - getRateDisplayValue(value) { - const numValue = this.getNumericValue(value); - if (Number.isNaN(numValue)) { - return ''; - } - return numValue.toString().replace('.', this.props.toLocaleDigit('.')).substring(0, value.length); - } - - getNumericValue(value) { - const numValue = parseFloat(value.toString().replace(this.props.toLocaleDigit('.'), '.')); - if (Number.isNaN(numValue)) { - return NaN; - } - return numValue.toFixed(3); - } + }, [props.translate, distanceCustomUnit, getUnitLabel, getRateLabel]); - fetchData() { + const fetchData = useCallback(() => { // Instead of setting the reimbursement account loading within the optimistic data of the API command, use a separate action so that the Onyx value is updated right away. // openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in // updating Onyx with the optimistic data. + const propsPolicyId = props.policy.id; BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(this.props.policy.id); - } - - render() { - const viewAllReceiptsUrl = `expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; - const distanceCustomUnit = _.find(lodashGet(this.props, 'policy.customUnits', {}), (unit) => unit.name === 'Distance'); - const distanceCustomRate = _.find(lodashGet(distanceCustomUnit, 'rates', {}), (rate) => rate.name === 'Default Rate'); - return ( - <> -
Link.openOldDotLink(viewAllReceiptsUrl), - icon: Expensicons.Receipt, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - link: () => Link.buildOldDotURL(viewAllReceiptsUrl), - }, - ]} - > - - - {this.props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')} - - {this.props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')} - - -
- -
- - {this.props.translate('workspace.reimburse.trackDistanceCopy')} - - - Navigation.navigate(ROUTES.getWorkspaceRateAndUnitRoute(this.props.policy.id))} - wrapperStyle={[styles.mhn5, styles.wAuto]} - brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} + Policy.openWorkspaceReimburseView(propsPolicyId); + }, [props.policy.id]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + useEffect(() => { + setCurrentRatePerUnit(getCurrentRatePerUnitLabel()); + const isReconnecting = prevIsOffline && !props.network.isOffline; + if (!isReconnecting) { + return; + } + fetchData(); + }, [props.policy.customUnits, props.preferredLocale, props.network.isOffline, prevIsOffline, getCurrentRatePerUnitLabel, fetchData]); + + return ( + <> +
Link.openOldDotLink(viewAllReceiptsUrl), + icon: Expensicons.Receipt, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewAllReceiptsUrl), + }, + ]} + > + + + {props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')} + - -
- - - - ); - } + {props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')} + + +
+ +
+ + {props.translate('workspace.reimburse.trackDistanceCopy')} + + + Navigation.navigate(ROUTES.getWorkspaceRateAndUnitRoute(props.policy.id))} + wrapperStyle={[styles.mhn5, styles.wAuto]} + brickRoadIndicator={(lodashGet(distanceCustomUnit, 'errors') || lodashGet(distanceCustomRate, 'errors')) && CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR} + /> + +
+ + + + ); } WorkspaceReimburseView.defaultProps = defaultProps; WorkspaceReimburseView.propTypes = propTypes; +WorkspaceReimburseView.displayName = 'WorkspaceReimburseView'; export default compose( withLocalize,