Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cancel function when you select an option in search and navigate away from the optionList #42471

17 changes: 17 additions & 0 deletions src/hooks/useCancelSearchOnModalClose.ts
Original file line number Diff line number Diff line change
@@ -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;
1 change: 1 addition & 0 deletions src/libs/API/parameters/SearchForReportsParams.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type SearchForReportsParams = {
searchInput: string;
canCancel?: boolean;
};

export default SearchForReportsParams;
29 changes: 21 additions & 8 deletions src/libs/HttpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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<AbortCommand, AbortController>();
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'];
Expand All @@ -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<Response> {
function processHTTPRequest(url: string, method: RequestType = 'get', body: FormData | null = null, abortSignal: AbortSignal | undefined = undefined): Promise<Response> {
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,
})
Expand Down Expand Up @@ -159,15 +168,19 @@ function xhr(command: string, data: Record<string, unknown>, 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 {
Expand Down
13 changes: 12 additions & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -351,7 +362,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
name={NAVIGATORS.RIGHT_MODAL_NAVIGATOR}
options={screenOptions.rightModalNavigator}
component={RightModalNavigator}
listeners={modalScreenListeners}
listeners={modalScreenListenersWithCancelSearch}
/>
<RootStack.Screen
name={NAVIGATORS.FULL_SCREEN_NAVIGATOR}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3551,7 +3551,7 @@ function searchForReports(searchInput: string, policyID?: string) {
];

const searchForRoomToMentionParams: SearchForRoomsToMentionParams = {query: searchInput, policyID: policyID ?? ''};
const searchForReportsParams: SearchForReportsParams = {searchInput};
const searchForReportsParams: SearchForReportsParams = {searchInput, canCancel: true};

API.read(policyID ? READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION : READ_COMMANDS.SEARCH_FOR_REPORTS, policyID ? searchForRoomToMentionParams : searchForReportsParams, {
successData,
Expand Down
2 changes: 2 additions & 0 deletions src/pages/ChatFinderPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {useOptionsList} from '@components/OptionListContextProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import UserListItem from '@components/SelectionList/UserListItem';
import useCancelSearchOnModalClose from '@hooks/useCancelSearchOnModalClose';
import useDebouncedState from '@hooks/useDebouncedState';
import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -69,6 +70,7 @@ function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPa
},
[setSearchValue, setSearchValueInServer],
);
useCancelSearchOnModalClose();

useEffect(() => {
Timing.start(CONST.TIMING.CHAT_FINDER_RENDER);
Expand Down
4 changes: 4 additions & 0 deletions src/pages/RoomInvitePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/pages/iou/request/step/IOURequestStepParticipants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/pages/tasks/TaskAssigneeSelectorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -158,6 +160,7 @@ function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalPro

const selectReport = useCallback(
(option: ListItem) => {
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);
if (!option) {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions src/pages/tasks/TaskShareDestinationSelectorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions src/pages/workspace/WorkspaceInvitePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down
Loading