diff --git a/src/libs/actions/OnyxUpdates.js b/src/libs/actions/OnyxUpdates.ts similarity index 62% rename from src/libs/actions/OnyxUpdates.js rename to src/libs/actions/OnyxUpdates.ts index 8e45e7dd2e66..50a4fdffc3ae 100644 --- a/src/libs/actions/OnyxUpdates.js +++ b/src/libs/actions/OnyxUpdates.ts @@ -1,28 +1,25 @@ -import Onyx from 'react-native-onyx'; -import _ from 'underscore'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; +import {Merge} from 'type-fest'; import PusherUtils from '../PusherUtils'; import ONYXKEYS from '../../ONYXKEYS'; import * as QueuedOnyxUpdates from './QueuedOnyxUpdates'; import CONST from '../../CONST'; +import {OnyxUpdatesFromServer, OnyxUpdateEvent, Request} from '../../types/onyx'; +import Response from '../../types/onyx/Response'; // This key needs to be separate from ONYXKEYS.ONYX_UPDATES_FROM_SERVER so that it can be updated without triggering the callback when the server IDs are updated. If that // callback were triggered it would lead to duplicate processing of server updates. -let lastUpdateIDAppliedToClient = 0; +let lastUpdateIDAppliedToClient: OnyxEntry = 0; Onyx.connect({ key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, callback: (val) => (lastUpdateIDAppliedToClient = val), }); -/** - * @param {Object} request - * @param {Object} response - * @returns {Promise} - */ -function applyHTTPSOnyxUpdates(request, response) { +function applyHTTPSOnyxUpdates(request: Request, response: Response) { console.debug('[OnyxUpdateManager] Applying https update'); // For most requests we can immediately update Onyx. For write requests we queue the updates and apply them after the sequential queue has flushed to prevent a replay effect in // the UI. See https://github.com/Expensify/App/issues/12775 for more info. - const updateHandler = request.data.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? QueuedOnyxUpdates.queueOnyxUpdates : Onyx.update; + const updateHandler = request?.data?.apiRequestType === CONST.API_REQUEST_TYPE.WRITE ? QueuedOnyxUpdates.queueOnyxUpdates : Onyx.update; // First apply any onyx data updates that are being sent back from the API. We wait for this to complete and then // apply successData or failureData. This ensures that we do not update any pending, loading, or other UI states contained @@ -46,55 +43,45 @@ function applyHTTPSOnyxUpdates(request, response) { }); } -/** - * @param {Array} updates - * @returns {Promise} - */ -function applyPusherOnyxUpdates(updates) { +function applyPusherOnyxUpdates(updates: OnyxUpdateEvent[]) { console.debug('[OnyxUpdateManager] Applying pusher update'); - const pusherEventPromises = _.map(updates, (update) => PusherUtils.triggerMultiEventHandler(update.eventType, update.data)); + const pusherEventPromises = updates.map((update) => PusherUtils.triggerMultiEventHandler(update.eventType, update.data)); return Promise.all(pusherEventPromises).then(() => { console.debug('[OnyxUpdateManager] Done applying Pusher update'); }); } /** - * @param {Object[]} updateParams - * @param {String} updateParams.type - * @param {Number} updateParams.lastUpdateID - * @param {Object} [updateParams.request] Exists if updateParams.type === 'https' - * @param {Object} [updateParams.response] Exists if updateParams.type === 'https' - * @param {Object} [updateParams.updates] Exists if updateParams.type === 'pusher' - * @returns {Promise} + * @param [updateParams.request] Exists if updateParams.type === 'https' + * @param [updateParams.response] Exists if updateParams.type === 'https' + * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ -function apply({lastUpdateID, type, request, response, updates}) { +function apply({lastUpdateID, type, request, response, updates}: Merge): Promise; +function apply({lastUpdateID, type, request, response, updates}: Merge): Promise; +function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFromServer): Promise | undefined { console.debug(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, {request, response, updates}); - if (lastUpdateID && lastUpdateID < lastUpdateIDAppliedToClient) { + if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) < lastUpdateIDAppliedToClient) { console.debug('[OnyxUpdateManager] Update received was older than current state, returning without applying the updates'); return Promise.resolve(); } - if (lastUpdateID && lastUpdateID > lastUpdateIDAppliedToClient) { - Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, lastUpdateID); + if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) > lastUpdateIDAppliedToClient) { + Onyx.merge(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, Number(lastUpdateID)); } - if (type === CONST.ONYX_UPDATE_TYPES.HTTPS) { + if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response) { return applyHTTPSOnyxUpdates(request, response); } - if (type === CONST.ONYX_UPDATE_TYPES.PUSHER) { + if (type === CONST.ONYX_UPDATE_TYPES.PUSHER && updates) { return applyPusherOnyxUpdates(updates); } } /** - * @param {Object[]} updateParams - * @param {String} updateParams.type - * @param {Object} [updateParams.request] Exists if updateParams.type === 'https' - * @param {Object} [updateParams.response] Exists if updateParams.type === 'https' - * @param {Object} [updateParams.updates] Exists if updateParams.type === 'pusher' - * @param {Number} [updateParams.lastUpdateID] - * @param {Number} [updateParams.previousUpdateID] + * @param [updateParams.request] Exists if updateParams.type === 'https' + * @param [updateParams.response] Exists if updateParams.type === 'https' + * @param [updateParams.updates] Exists if updateParams.type === 'pusher' */ -function saveUpdateInformation(updateParams) { +function saveUpdateInformation(updateParams: OnyxUpdatesFromServer) { // Always use set() here so that the updateParams are never merged and always unique to the request that came in Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, updateParams); } @@ -102,10 +89,9 @@ function saveUpdateInformation(updateParams) { /** * This function will receive the previousUpdateID from any request/pusher update that has it, compare to our current app state * and return if an update is needed - * @param {Number} previousUpdateID The previousUpdateID contained in the response object - * @returns {Boolean} + * @param previousUpdateID The previousUpdateID contained in the response object */ -function doesClientNeedToBeUpdated(previousUpdateID = 0) { +function doesClientNeedToBeUpdated(previousUpdateID = 0): boolean { // If no previousUpdateID is sent, this is not a WRITE request so we don't need to update our current state if (!previousUpdateID) { return false; diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index b44b956ac547..84ea24f4da6b 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -10,14 +10,13 @@ import MoneyRequestParticipantsSplitSelector from './MoneyRequestParticipantsSpl import MoneyRequestParticipantsSelector from './MoneyRequestParticipantsSelector'; import styles from '../../../../styles/styles'; import ScreenWrapper from '../../../../components/ScreenWrapper'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; import Navigation from '../../../../libs/Navigation/Navigation'; -import compose from '../../../../libs/compose'; import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; import * as IOU from '../../../../libs/actions/IOU'; import * as MoneyRequestUtils from '../../../../libs/MoneyRequestUtils'; import {iouPropTypes, iouDefaultProps} from '../../propTypes'; +import useLocalize from '../../../../hooks/useLocalize'; const propTypes = { /** React Navigation route */ @@ -37,8 +36,6 @@ const propTypes = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab: PropTypes.oneOf([CONST.TAB.DISTANCE, CONST.TAB.MANUAL, CONST.TAB.SCAN]).isRequired, - - ...withLocalizePropTypes, }; const defaultProps = { @@ -46,16 +43,32 @@ const defaultProps = { }; function MoneyRequestParticipantsPage(props) { + const {translate} = useLocalize(); const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); + const isNewReportIDSelectedLocally = useRef(false); const optionsSelectorRef = useRef(); const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); - const navigateToNextStep = () => { + const splitNavigateToNextStep = () => { Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType.current, reportID.current)); }; + const moneyRequestNavigateToNextStep = (option) => { + isNewReportIDSelectedLocally.current = true; + + if (!option.reportID) { + IOU.setMoneyRequestId(iouType.current); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType.current, reportID.current)); + + return; + } + + IOU.setMoneyRequestId(`${iouType.current}${option.reportID}`); + Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType.current, option.reportID)); + }; + const navigateBack = (forceFallback = false) => { Navigation.goBack(ROUTES.getMoneyRequestRoute(iouType.current, reportID.current), forceFallback); }; @@ -64,7 +77,7 @@ function MoneyRequestParticipantsPage(props) { // ID in Onyx could change by initiating a new request in a separate browser tab or completing a request if (prevMoneyRequestId.current !== props.iou.id) { // The ID is cleared on completing a request. In that case, we will do nothing - if (!isDistanceRequest && props.iou.id) { + if (!isNewReportIDSelectedLocally.current && !isDistanceRequest && props.iou.id) { navigateBack(true); } return; @@ -72,7 +85,7 @@ function MoneyRequestParticipantsPage(props) { // Reset the money request Onyx if the ID in Onyx does not match the ID from params const moneyRequestId = `${iouType.current}${reportID.current}`; - const shouldReset = props.iou.id !== moneyRequestId; + const shouldReset = props.iou.id !== moneyRequestId && !isNewReportIDSelectedLocally.current; if (shouldReset) { IOU.resetMoneyRequestInfo(moneyRequestId); } @@ -94,12 +107,12 @@ function MoneyRequestParticipantsPage(props) { {({safeAreaPaddingBottomStyle}) => ( {iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.SPLIT ? ( (optionsSelectorRef.current = el)} - onStepComplete={navigateToNextStep} + onStepComplete={moneyRequestNavigateToNextStep} onAddParticipants={IOU.setMoneyRequestParticipants} safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} iouType={iouType.current} @@ -124,12 +137,11 @@ MoneyRequestParticipantsPage.displayName = 'IOUParticipantsPage'; MoneyRequestParticipantsPage.propTypes = propTypes; MoneyRequestParticipantsPage.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - iou: {key: ONYXKEYS.IOU}, - selectedTab: { - key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, - }, - }), -)(MoneyRequestParticipantsPage); +export default withOnyx({ + iou: { + key: ONYXKEYS.IOU, + }, + selectedTab: { + key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`, + }, +})(MoneyRequestParticipantsPage); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 693a55b14e07..e12672650a54 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -153,7 +153,7 @@ class MoneyRequestParticipantsSelector extends Component { */ addSingleParticipant(option) { this.props.onAddParticipants([{accountID: option.accountID, login: option.login, isPolicyExpenseChat: option.isPolicyExpenseChat, reportID: option.reportID, selected: true}]); - this.props.onStepComplete(); + this.props.onStepComplete(option); } render() { diff --git a/src/types/onyx/OnyxUpdatesFromServer.ts b/src/types/onyx/OnyxUpdatesFromServer.ts index 02a96d4ce230..50b1503b90bd 100644 --- a/src/types/onyx/OnyxUpdatesFromServer.ts +++ b/src/types/onyx/OnyxUpdatesFromServer.ts @@ -2,13 +2,18 @@ import {OnyxUpdate} from 'react-native-onyx'; import Request from './Request'; import Response from './Response'; +type OnyxUpdateEvent = { + eventType: string; + data: OnyxUpdate[]; +}; + type OnyxUpdatesFromServer = { type: 'https' | 'pusher'; lastUpdateID: number | string; previousUpdateID: number | string; request?: Request; response?: Response; - updates?: OnyxUpdate[]; + updates?: OnyxUpdateEvent[]; }; -export default OnyxUpdatesFromServer; +export type {OnyxUpdatesFromServer, OnyxUpdateEvent}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index a980e086aff5..a7bbaf848265 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -33,7 +33,7 @@ import ReimbursementAccountDraft from './ReimbursementAccountDraft'; import WalletTransfer from './WalletTransfer'; import ReceiptModal from './ReceiptModal'; import MapboxAccessToken from './MapboxAccessToken'; -import OnyxUpdatesFromServer from './OnyxUpdatesFromServer'; +import {OnyxUpdatesFromServer, OnyxUpdateEvent} from './OnyxUpdatesFromServer'; import Download from './Download'; import PolicyMember from './PolicyMember'; import Policy from './Policy'; @@ -97,6 +97,7 @@ export type { Form, AddDebitCardForm, OnyxUpdatesFromServer, + OnyxUpdateEvent, RecentWaypoints, RecentlyUsedCategories, RecentlyUsedTags,