diff --git a/src/hooks/useCancelSearchOnModalClose.ts b/src/hooks/useCancelSearchOnModalClose.ts new file mode 100644 index 000000000000..5f92937cce24 --- /dev/null +++ b/src/hooks/useCancelSearchOnModalClose.ts @@ -0,0 +1,17 @@ +import {useNavigation} from '@react-navigation/native'; +import {useEffect} from 'react'; +import {READ_COMMANDS} from '@libs/API/types'; +import HttpUtils from '@libs/HttpUtils'; + +const useCancelSearchOnModalClose = () => { + const navigation = useNavigation(); + useEffect(() => { + const unsubscribe = navigation.addListener('beforeRemove', () => { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); + }); + + return unsubscribe; + }, [navigation]); +}; + +export default useCancelSearchOnModalClose; diff --git a/src/libs/API/parameters/SearchForReportsParams.ts b/src/libs/API/parameters/SearchForReportsParams.ts index b6d1bbadb1dc..5cc90e39ffed 100644 --- a/src/libs/API/parameters/SearchForReportsParams.ts +++ b/src/libs/API/parameters/SearchForReportsParams.ts @@ -1,5 +1,6 @@ type SearchForReportsParams = { searchInput: string; + canCancel?: boolean; }; export default SearchForReportsParams; diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index 842776d0c8dd..b254303d1784 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -7,13 +7,20 @@ import type {RequestType} from '@src/types/onyx/Request'; import type Response from '@src/types/onyx/Response'; import * as NetworkActions from './actions/Network'; import * as UpdateRequired from './actions/UpdateRequired'; -import {SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from './API/types'; +import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from './API/types'; import * as ApiUtils from './ApiUtils'; import HttpsError from './Errors/HttpsError'; let shouldFailAllRequests = false; let shouldForceOffline = false; +const ABORT_COMMANDS = { + All: 'All', + [READ_COMMANDS.SEARCH_FOR_REPORTS]: READ_COMMANDS.SEARCH_FOR_REPORTS, +} as const; + +type AbortCommand = keyof typeof ABORT_COMMANDS; + Onyx.connect({ key: ONYXKEYS.NETWORK, callback: (network) => { @@ -26,7 +33,9 @@ Onyx.connect({ }); // We use the AbortController API to terminate pending request in `cancelPendingRequests` -let cancellationController = new AbortController(); +const abortControllerMap = new Map(); +abortControllerMap.set(ABORT_COMMANDS.All, new AbortController()); +abortControllerMap.set(ABORT_COMMANDS.SearchForReports, new AbortController()); // Some existing old commands (6+ years) exempted from the auth writes count check const exemptedCommandsWithAuthWrites: string[] = ['SetWorkspaceAutoReportingFrequency']; @@ -45,11 +54,11 @@ const APICommandRegex = /\/api\/([^&?]+)\??.*/; * Send an HTTP request, and attempt to resolve the json response. * If there is a network error, we'll set the application offline. */ -function processHTTPRequest(url: string, method: RequestType = 'get', body: FormData | null = null, canCancel = true): Promise { +function processHTTPRequest(url: string, method: RequestType = 'get', body: FormData | null = null, abortSignal: AbortSignal | undefined = undefined): Promise { const startTime = new Date().valueOf(); return fetch(url, { // We hook requests to the same Controller signal, so we can cancel them all at once - signal: canCancel ? cancellationController.signal : undefined, + signal: abortSignal, method, body, }) @@ -159,15 +168,19 @@ function xhr(command: string, data: Record, type: RequestType = }); const url = ApiUtils.getCommandURL({shouldUseSecure, command}); - return processHTTPRequest(url, type, formData, Boolean(data.canCancel)); + + const abortSignalController = data.canCancel ? abortControllerMap.get(command as AbortCommand) ?? abortControllerMap.get(ABORT_COMMANDS.All) : undefined; + return processHTTPRequest(url, type, formData, abortSignalController?.signal); } -function cancelPendingRequests() { - cancellationController.abort(); +function cancelPendingRequests(command: AbortCommand = ABORT_COMMANDS.All) { + const controller = abortControllerMap.get(command) ?? abortControllerMap.get(ABORT_COMMANDS.All); + + controller?.abort(); // We create a new instance because once `abort()` is called any future requests using the same controller would // automatically get rejected: https://dom.spec.whatwg.org/#abortcontroller-api-integration - cancellationController = new AbortController(); + abortControllerMap.set(command, new AbortController()); } export default { diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index ad437f08523c..f9b1c5cbeb0c 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -7,6 +7,8 @@ import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {READ_COMMANDS} from '@libs/API/types'; +import HttpUtils from '@libs/HttpUtils'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; @@ -157,6 +159,15 @@ const modalScreenListeners = { }, }; +// Extended modal screen listeners with additional cancellation of pending requests +const modalScreenListenersWithCancelSearch = { + ...modalScreenListeners, + beforeRemove: () => { + modalScreenListeners.beforeRemove(); + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); + }, +}; + function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -351,7 +362,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie name={NAVIGATORS.RIGHT_MODAL_NAVIGATOR} options={screenOptions.rightModalNavigator} component={RightModalNavigator} - listeners={modalScreenListeners} + listeners={modalScreenListenersWithCancelSearch} /> { Timing.start(CONST.TIMING.CHAT_FINDER_RENDER); diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index c78094594f16..45f85c0cdaa9 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -18,7 +18,9 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActions from '@libs/actions/Report'; +import {READ_COMMANDS} from '@libs/API/types'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import HttpUtils from '@libs/HttpUtils'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -171,6 +173,8 @@ function RoomInvitePage({ }, [isPolicyEmployee, reportID, role]); const reportName = useMemo(() => ReportUtils.getReportName(report), [report]); const inviteUsers = useCallback(() => { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); + if (!validate()) { return; } diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index e0be24db5c85..189e204e810b 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -4,7 +4,9 @@ import {withOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {READ_COMMANDS} from '@libs/API/types'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import HttpUtils from '@libs/HttpUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; @@ -84,6 +86,7 @@ function IOURequestStepParticipants({ const addParticipant = useCallback( (val: Participant[]) => { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); IOU.setMoneyRequestParticipants(transactionID, val); const rateID = DistanceRequestUtils.getCustomUnitRateID(val[0]?.reportID ?? ''); IOU.setCustomUnitRateID(transactionID, rateID); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 3116b8a84152..e8fe2926a7b6 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -22,6 +22,8 @@ import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActions from '@libs/actions/Report'; +import {READ_COMMANDS} from '@libs/API/types'; +import HttpUtils from '@libs/HttpUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -158,6 +160,7 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro const selectReport = useCallback( (option: ListItem) => { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); if (!option) { return; } diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx index 4d731d59a9e7..445ec2741583 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -11,6 +11,8 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActions from '@libs/actions/Report'; +import {READ_COMMANDS} from '@libs/API/types'; +import HttpUtils from '@libs/HttpUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -21,6 +23,7 @@ import ROUTES from '@src/ROUTES'; import type {Report} from '@src/types/onyx'; const selectReportHandler = (option: unknown) => { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); const optionItem = option as ReportUtils.OptionData; if (!optionItem || !optionItem?.reportID) { diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index c564ce00542b..666570805c57 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -17,7 +17,9 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportActions from '@libs/actions/Report'; +import {READ_COMMANDS} from '@libs/API/types'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import HttpUtils from '@libs/HttpUtils'; import * as LoginUtils from '@libs/LoginUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; @@ -233,6 +235,7 @@ function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, poli if (!isValid) { return; } + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); const invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs = {}; selectedOptions.forEach((option) => {