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',
SearchForReports: READ_COMMANDS.SEARCH_FOR_REPORTS,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to keep the names in sync? if true probably something like this is better

const ABORT_COMMANDS = {
    All: 'All',
    [READ_COMMANDS.SEARCH_FOR_REPORTS]: READ_COMMANDS.SEARCH_FOR_REPORTS,
} as const;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! Good point!

} as const;

type AbortCommand = (typeof ABORT_COMMANDS)[keyof typeof ABORT_COMMANDS];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified with

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
2 changes: 1 addition & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3546,7 +3546,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 @@ -61,6 +62,7 @@ function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPa
const offlineMessage: MaybePhraseKey = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : '';

const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
useCancelSearchOnModalClose();

useEffect(() => {
Timing.start(CONST.TIMING.CHAT_FINDER_RENDER);
Expand Down
2 changes: 2 additions & 0 deletions src/pages/NewChatSelectorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import TabSelector from '@components/TabSelector/TabSelector';
import useCancelSearchOnModalClose from '@hooks/useCancelSearchOnModalClose';
import useLocalize from '@hooks/useLocalize';
import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator';
import CONST from '@src/CONST';
Expand All @@ -12,6 +13,7 @@ import WorkspaceNewRoomPage from './workspace/WorkspaceNewRoomPage';
function NewChatSelectorPage() {
const {translate} = useLocalize();
const navigation = useNavigation();
useCancelSearchOnModalClose();

return (
<ScreenWrapper
Expand Down
Loading