diff --git a/src/pages/iou/request/step/DiscardChangesConfirmation/index.native.tsx b/src/pages/iou/request/step/DiscardChangesConfirmation/index.native.tsx index b5ecb1e0a2bf..31cae7433fd7 100644 --- a/src/pages/iou/request/step/DiscardChangesConfirmation/index.native.tsx +++ b/src/pages/iou/request/step/DiscardChangesConfirmation/index.native.tsx @@ -1,7 +1,7 @@ import type {NavigationAction} from '@react-navigation/native'; +import {usePreventRemove} from '@react-navigation/native'; import React, {memo, useCallback, useRef, useState} from 'react'; import ConfirmModal from '@components/ConfirmModal'; -import useBeforeRemove from '@hooks/useBeforeRemove'; import useLocalize from '@hooks/useLocalize'; import navigationRef from '@libs/Navigation/navigationRef'; import type DiscardChangesConfirmationProps from './types'; @@ -9,21 +9,18 @@ import type DiscardChangesConfirmationProps from './types'; function DiscardChangesConfirmation({getHasUnsavedChanges}: DiscardChangesConfirmationProps) { const {translate} = useLocalize(); const [isVisible, setIsVisible] = useState(false); + const shouldAllowNavigation = useRef(false); const blockedNavigationAction = useRef(undefined); - useBeforeRemove( - useCallback( - (e) => { - if (!getHasUnsavedChanges()) { - return; - } + const hasUnsavedChanges = getHasUnsavedChanges(); + const shouldPrevent = hasUnsavedChanges && !shouldAllowNavigation.current; - e.preventDefault(); - blockedNavigationAction.current = e.data.action; - setIsVisible(true); - }, - [getHasUnsavedChanges], - ), + usePreventRemove( + shouldPrevent, + useCallback(({data}) => { + blockedNavigationAction.current = data.action; + setIsVisible(true); + }, []), ); return ( @@ -36,11 +33,18 @@ function DiscardChangesConfirmation({getHasUnsavedChanges}: DiscardChangesConfir cancelText={translate('common.cancel')} onConfirm={() => { setIsVisible(false); + shouldAllowNavigation.current = true; if (blockedNavigationAction.current) { navigationRef.current?.dispatch(blockedNavigationAction.current); + blockedNavigationAction.current = undefined; + } else { + navigationRef.current?.goBack(); } }} - onCancel={() => setIsVisible(false)} + onCancel={() => { + setIsVisible(false); + blockedNavigationAction.current = undefined; + }} shouldHandleNavigationBack /> ); diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index 204191ea1c26..c9502d730949 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -1,5 +1,5 @@ import lodashIsEmpty from 'lodash/isEmpty'; -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -69,8 +69,9 @@ function IOURequestStepDescription({ return isEditingSplit && !lodashIsEmpty(splitDraftTransaction) ? (splitDraftTransaction?.comment?.comment ?? '') : (transaction?.comment?.comment ?? ''); }, [isTransactionDraft, iouType, isEditingSplit, splitDraftTransaction, transaction?.comment?.comment]); - const descriptionRef = useRef(currentDescriptionInMarkdown); - const isSavedRef = useRef(false); + const [currentDescription, setCurrentDescription] = useState(currentDescriptionInMarkdown); + const [isSaved, setIsSaved] = useState(false); + const shouldNavigateAfterSaveRef = useRef(false); useRestartOnReceiptFailure(transaction, reportID, iouType, action); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; @@ -116,12 +117,20 @@ function IOURequestStepDescription({ [isDescriptionRequired, translate], ); - const navigateBack = () => { + const navigateBack = useCallback(() => { Navigation.goBack(backTo); - }; + }, [backTo]); + + useEffect(() => { + if (!isSaved || !shouldNavigateAfterSaveRef.current) { + return; + } + shouldNavigateAfterSaveRef.current = false; + navigateBack(); + }, [isSaved, navigateBack]); const updateDescriptionRef = (value: string) => { - descriptionRef.current = value; + setCurrentDescription(value); }; const updateComment = (value: FormOnyxValues) => { @@ -129,19 +138,18 @@ function IOURequestStepDescription({ return; } - isSavedRef.current = true; const newComment = value.moneyRequestComment.trim(); - // Only update comment if it has changed if (newComment === currentDescriptionInMarkdown) { - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; return; } - // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit) { setDraftSplitTransaction(transaction?.transactionID, splitDraftTransaction, {comment: newComment}); - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; return; } @@ -161,7 +169,8 @@ function IOURequestStepDescription({ ); } - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; }; // eslint-disable-next-line rulesdir/no-negated-variables @@ -218,10 +227,10 @@ function IOURequestStepDescription({ }); }} getHasUnsavedChanges={() => { - if (isSavedRef.current) { + if (isSaved) { return false; } - return descriptionRef.current !== currentDescriptionInMarkdown; + return currentDescription !== currentDescriptionInMarkdown; }} /> diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx index 430b9e7a6bb7..fe960e38e1d2 100644 --- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx +++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useRef} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; @@ -56,8 +56,9 @@ function IOURequestStepMerchant({ const merchant = getTransactionDetails(isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction)?.merchant; const isEmptyMerchant = merchant === '' || merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const initialMerchant = isEmptyMerchant ? '' : merchant; - const merchantRef = useRef(initialMerchant); - const isSavedRef = useRef(false); + const [currentMerchant, setCurrentMerchant] = useState(initialMerchant); + const [isSaved, setIsSaved] = useState(false); + const shouldNavigateAfterSaveRef = useRef(false); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; @@ -66,9 +67,17 @@ function IOURequestStepMerchant({ const isMerchantRequired = isPolicyExpenseChat(report) || isExpenseRequest(report) || transaction?.participants?.some((participant) => !!participant.isPolicyExpenseChat); - const navigateBack = () => { + const navigateBack = useCallback(() => { Navigation.goBack(backTo); - }; + }, [backTo]); + + useEffect(() => { + if (!isSaved || !shouldNavigateAfterSaveRef.current) { + return; + } + shouldNavigateAfterSaveRef.current = false; + navigateBack(); + }, [isSaved, navigateBack]); const validate = useCallback( (value: FormOnyxValues) => { @@ -92,27 +101,24 @@ function IOURequestStepMerchant({ ); const updateMerchantRef = (value: string) => { - merchantRef.current = value; + setCurrentMerchant(value); }; const updateMerchant = (value: FormOnyxValues) => { - isSavedRef.current = true; const newMerchant = value.moneyRequestMerchant?.trim(); - // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { setDraftSplitTransaction(transactionID, splitDraftTransaction, {merchant: newMerchant}); - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; return; } - // In case the merchant hasn't been changed, do not make the API request. - // In case the merchant has been set to empty string while current merchant is partial, do nothing too. if (newMerchant === merchant || (newMerchant === '' && merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT)) { - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; return; } - // When creating/editing an expense, newMerchant can be blank so we fall back on PARTIAL_TRANSACTION_MERCHANT setMoneyRequestMerchant(transactionID, newMerchant || CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, !isEditing); if (isEditing) { updateMoneyRequestMerchant( @@ -127,7 +133,8 @@ function IOURequestStepMerchant({ isASAPSubmitBetaEnabled, ); } - navigateBack(); + setIsSaved(true); + shouldNavigateAfterSaveRef.current = true; }; return ( @@ -171,10 +178,10 @@ function IOURequestStepMerchant({ }); }} getHasUnsavedChanges={() => { - if (isSavedRef.current) { + if (isSaved) { return false; } - return merchantRef.current !== initialMerchant; + return currentMerchant !== initialMerchant; }} />