Skip to content

Commit 8956422

Browse files
authored
Merge pull request Expensify#65421 from software-mansion-labs/@szymczak/performance-improvements
Reports page and TransactionItemRow performance improvements[part 1]
2 parents fda04c5 + af258b3 commit 8956422

28 files changed

Lines changed: 284 additions & 274 deletions

src/ONYXKEYS.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,7 @@ type OnyxValuesMapping = {
11561156
[ONYXKEYS.REVIEW_DUPLICATES]: OnyxTypes.ReviewDuplicates;
11571157
[ONYXKEYS.ADD_NEW_COMPANY_CARD]: OnyxTypes.AddNewCompanyCardFeed;
11581158
[ONYXKEYS.ASSIGN_CARD]: OnyxTypes.AssignCard;
1159-
[ONYXKEYS.MOBILE_SELECTION_MODE]: OnyxTypes.MobileSelectionMode;
1159+
[ONYXKEYS.MOBILE_SELECTION_MODE]: boolean;
11601160
[ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL]: string;
11611161
[ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL]: string;
11621162
[ONYXKEYS.NVP_BILLING_FUND_ID]: number;

src/components/MoneyReportHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,9 +892,9 @@ function MoneyReportHeader({
892892
[connectedIntegrationName, styles.noWrap, styles.textStrong, translate],
893893
);
894894

895-
const {selectionMode} = useMobileSelectionMode();
895+
const isMobileSelectionModeEnabled = useMobileSelectionMode();
896896

897-
if (selectionMode?.isEnabled) {
897+
if (isMobileSelectionModeEnabled) {
898898
return (
899899
<HeaderWithBackButton
900900
title={translate('common.selectMultiple')}

src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ function MoneyRequestReportActionsList({
156156

157157
const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext();
158158

159-
const {selectionMode} = useMobileSelectionMode();
159+
const isMobileSelectionModeEnabled = useMobileSelectionMode();
160160
const {
161161
options: selectedTransactionsOptions,
162162
handleDeleteTransactions,
@@ -557,7 +557,7 @@ function MoneyRequestReportActionsList({
557557
style={[styles.flex1]}
558558
ref={wrapperViewRef}
559559
>
560-
{shouldUseNarrowLayout && !!selectionMode?.isEnabled && (
560+
{shouldUseNarrowLayout && isMobileSelectionModeEnabled && (
561561
<>
562562
<ButtonWithDropdownMenu
563563
onPress={() => null}

src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function MoneyRequestReportTransactionList({
136136
const {isMouseDownOnInput, setMouseUp} = useMouseContext();
137137

138138
const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext();
139-
const {selectionMode} = useMobileSelectionMode();
139+
const isMobileSelectionModeEnabled = useMobileSelectionMode();
140140

141141
const toggleTransaction = useCallback(
142142
(transactionID: string) => {
@@ -273,7 +273,7 @@ function MoneyRequestReportTransactionList({
273273
return;
274274
}
275275

276-
if (selectionMode?.isEnabled) {
276+
if (isMobileSelectionModeEnabled) {
277277
toggleTransaction(transaction.transactionID);
278278
return;
279279
}
@@ -295,7 +295,7 @@ function MoneyRequestReportTransactionList({
295295
if (!isSmallScreenWidth) {
296296
return;
297297
}
298-
if (selectionMode?.isEnabled) {
298+
if (isMobileSelectionModeEnabled) {
299299
toggleTransaction(transaction.transactionID);
300300
return;
301301
}
@@ -312,7 +312,7 @@ function MoneyRequestReportTransactionList({
312312
taxAmountColumnSize={taxAmountColumnSize}
313313
shouldShowTooltip
314314
shouldUseNarrowLayout={shouldUseNarrowLayout || isMediumScreenWidth}
315-
shouldShowCheckbox={!!selectionMode?.isEnabled || !isSmallScreenWidth}
315+
shouldShowCheckbox={isMobileSelectionModeEnabled || !isSmallScreenWidth}
316316
onCheckboxPress={toggleTransaction}
317317
columns={allReportColumns}
318318
scrollToNewTransaction={transaction.transactionID === newTransactions?.at(0)?.transactionID ? scrollToNewTransaction : undefined}
@@ -355,7 +355,7 @@ function MoneyRequestReportTransactionList({
355355
title={translate('common.select')}
356356
icon={Expensicons.CheckSquare}
357357
onPress={() => {
358-
if (!selectionMode?.isEnabled) {
358+
if (!isMobileSelectionModeEnabled) {
359359
turnOnMobileSelectionMode();
360360
}
361361
toggleTransaction(selectedTransactionID);

src/components/Navigation/SearchSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function SearchSidebar({state}: SearchSidebarProps) {
5757
}
5858
}, [lastSearchType, queryJSON, setLastSearchType, currentSearchResults]);
5959

60-
const isDataLoaded = isSearchDataLoaded(currentSearchResults, lastNonEmptySearchResults, queryJSON);
60+
const isDataLoaded = isSearchDataLoaded(currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults, queryJSON);
6161
const shouldShowLoadingState = route?.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT ? false : !isOffline && !isDataLoaded;
6262

6363
if (shouldUseNarrowLayout) {

src/components/Search/SearchContext.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {isMoneyRequestReport} from '@libs/ReportUtils';
33
import {isTransactionCardGroupListItemType, isTransactionListItemType, isTransactionMemberGroupListItemType, isTransactionReportGroupListItemType} from '@libs/SearchUIUtils';
44
import CONST from '@src/CONST';
55
import type ChildrenProps from '@src/types/utils/ChildrenProps';
6+
import {isEmptyObject} from '@src/types/utils/EmptyObject';
67
import type {SearchContext, SearchContextData} from './types';
78

89
const defaultSearchContextData: SearchContextData = {
@@ -41,10 +42,15 @@ function SearchContextProvider({children}: ChildrenProps) {
4142
const areTransactionsEmpty = useRef(true);
4243

4344
const setCurrentSearchHash = useCallback((searchHash: number) => {
44-
setSearchContextData((prevState) => ({
45-
...prevState,
46-
currentSearchHash: searchHash,
47-
}));
45+
setSearchContextData((prevState) => {
46+
if (searchHash === prevState.currentSearchHash) {
47+
return prevState;
48+
}
49+
return {
50+
...prevState,
51+
currentSearchHash: searchHash,
52+
};
53+
});
4854
}, []);
4955

5056
const setSelectedTransactions: SearchContext['setSelectedTransactions'] = useCallback((selectedTransactions, data = []) => {
@@ -107,6 +113,10 @@ function SearchContextProvider({children}: ChildrenProps) {
107113
if (searchHashOrClearIDsFlag === searchContextData.currentSearchHash) {
108114
return;
109115
}
116+
117+
if (searchContextData.selectedReports.length === 0 && isEmptyObject(searchContextData.selectedTransactions) && !searchContextData.shouldTurnOffSelectionMode) {
118+
return;
119+
}
110120
setSearchContextData((prevState) => ({
111121
...prevState,
112122
shouldTurnOffSelectionMode,
@@ -116,7 +126,13 @@ function SearchContextProvider({children}: ChildrenProps) {
116126
setShouldShowExportModeOption(false);
117127
setExportMode(false);
118128
},
119-
[searchContextData.currentSearchHash, setSelectedTransactions],
129+
[
130+
searchContextData.currentSearchHash,
131+
searchContextData.selectedReports.length,
132+
searchContextData.selectedTransactions,
133+
searchContextData.shouldTurnOffSelectionMode,
134+
setSelectedTransactions,
135+
],
120136
);
121137

122138
const removeTransaction: SearchContext['removeTransaction'] = useCallback(

src/components/Search/SearchList.tsx

Lines changed: 12 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@ import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
2020
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
2121
import useKeyboardState from '@hooks/useKeyboardState';
2222
import useLocalize from '@hooks/useLocalize';
23-
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
2423
import useOnyx from '@hooks/useOnyx';
2524
import useResponsiveLayout from '@hooks/useResponsiveLayout';
2625
import useSafeAreaPaddings from '@hooks/useSafeAreaPaddings';
2726
import useThemeStyles from '@hooks/useThemeStyles';
28-
import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
27+
import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
2928
import {isMobileChrome} from '@libs/Browser';
3029
import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener';
3130
import variables from '@styles/variables';
@@ -78,6 +77,13 @@ type SearchListProps = Pick<FlatListPropsWithLayout<SearchListItem>, 'onScroll'
7877

7978
/** Invoked on mount and layout changes */
8079
onLayout?: () => void;
80+
81+
/** Whether mobile selection mode is enabled */
82+
isMobileSelectionModeEnabled: boolean;
83+
};
84+
85+
const keyExtractor = (item: SearchListItem, index: number) => {
86+
return item.keyForList ?? `${index}`;
8187
};
8288

8389
const onScrollToIndexFailed = () => {};
@@ -102,6 +108,7 @@ function SearchList(
102108
queryJSON,
103109
onViewableItemsChanged,
104110
onLayout,
111+
isMobileSelectionModeEnabled,
105112
}: SearchListProps,
106113
ref: ForwardedRef<SearchListHandle>,
107114
) {
@@ -126,51 +133,14 @@ function SearchList(
126133
const {isSmallScreenWidth} = useResponsiveLayout();
127134

128135
const [isModalVisible, setIsModalVisible] = useState(false);
129-
const {selectionMode} = useMobileSelectionMode();
130136
const [longPressedItem, setLongPressedItem] = useState<SearchListItem>();
131-
// Check if selection should be on when the modal is opened
132-
const wasSelectionOnRef = useRef(false);
133-
// Keep track of the number of selected items to determine if we should turn off selection mode
134-
const selectionRef = useRef(0);
135137

136138
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
137139
canBeMissing: true,
138140
});
139141

140142
const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false});
141143

142-
useEffect(() => {
143-
selectionRef.current = selectedItemsLength;
144-
145-
if (!isSmallScreenWidth) {
146-
if (selectedItemsLength === 0) {
147-
turnOffMobileSelectionMode();
148-
}
149-
return;
150-
}
151-
if (!isFocused) {
152-
return;
153-
}
154-
if (!wasSelectionOnRef.current && selectedItemsLength > 0) {
155-
wasSelectionOnRef.current = true;
156-
}
157-
if (selectedItemsLength > 0 && !selectionMode?.isEnabled) {
158-
turnOnMobileSelectionMode();
159-
} else if (selectedItemsLength === 0 && selectionMode?.isEnabled && !wasSelectionOnRef.current) {
160-
turnOffMobileSelectionMode();
161-
}
162-
}, [selectionMode, isSmallScreenWidth, isFocused, selectedItemsLength]);
163-
164-
useEffect(
165-
() => () => {
166-
if (selectionRef.current !== 0) {
167-
return;
168-
}
169-
turnOffMobileSelectionMode();
170-
},
171-
[],
172-
);
173-
174144
const handleLongPressRow = useCallback(
175145
(item: SearchListItem) => {
176146
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
@@ -181,14 +151,14 @@ function SearchList(
181151
if ('transactions' in item && item.transactions.length === 0) {
182152
return;
183153
}
184-
if (selectionMode?.isEnabled) {
154+
if (isMobileSelectionModeEnabled) {
185155
onCheckboxPress(item);
186156
return;
187157
}
188158
setLongPressedItem(item);
189159
setIsModalVisible(true);
190160
},
191-
[isFocused, isSmallScreenWidth, onCheckboxPress, selectionMode?.isEnabled, shouldPreventLongPressRow],
161+
[isFocused, isSmallScreenWidth, onCheckboxPress, isMobileSelectionModeEnabled, shouldPreventLongPressRow],
192162
);
193163

194164
const turnOnSelectionMode = useCallback(() => {
@@ -398,7 +368,7 @@ function SearchList(
398368
<Animated.FlatList
399369
data={data}
400370
renderItem={renderItem}
401-
keyExtractor={(item, index) => item.keyForList ?? `${index}`}
371+
keyExtractor={keyExtractor}
402372
onScroll={onScroll}
403373
contentContainerStyle={contentContainerStyle}
404374
showsVerticalScrollIndicator={false}

src/components/Search/SearchPageHeader/SearchFiltersBar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ import type {SearchHeaderOptionValue} from './SearchPageHeader';
4444
type SearchFiltersBarProps = {
4545
queryJSON: SearchQueryJSON;
4646
headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>;
47+
isMobileSelectionModeEnabled: boolean;
4748
};
4849

49-
function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarProps) {
50+
function SearchFiltersBar({queryJSON, headerButtonsOptions, isMobileSelectionModeEnabled}: SearchFiltersBarProps) {
5051
const scrollRef = useRef<RNScrollView>(null);
5152

5253
// type, groupBy and status values are not guaranteed to respect the ts type as they come from user input
@@ -70,7 +71,6 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
7071
const [policyTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {canBeMissing: true});
7172
const [policyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, {canBeMissing: true});
7273
const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: true});
73-
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true});
7474
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true});
7575
const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: (data) => data?.errors});
7676

@@ -79,7 +79,7 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
7979
const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions ?? {}), [selectedTransactions]);
8080

8181
const hasErrors = Object.keys(searchResultsErrors ?? {}).length > 0 && !isOffline;
82-
const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled));
82+
const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || isMobileSelectionModeEnabled);
8383

8484
const [typeOptions, type] = useMemo(() => {
8585
const options = getTypeOptions(allPolicies, email);

src/components/Search/SearchPageHeader/SearchPageHeader.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import {View} from 'react-native';
33
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
44
import {useSearchContext} from '@components/Search/SearchContext';
55
import type {SearchQueryJSON} from '@components/Search/types';
6-
import useOnyx from '@hooks/useOnyx';
76
import useResponsiveLayout from '@hooks/useResponsiveLayout';
87
import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow';
98
import type CONST from '@src/CONST';
10-
import ONYXKEYS from '@src/ONYXKEYS';
119
import type DeepValueOf from '@src/types/utils/DeepValueOf';
1210
import SearchPageHeaderInput from './SearchPageHeaderInput';
1311

@@ -18,18 +16,26 @@ type SearchPageHeaderProps = {
1816
onSearchRouterFocus?: () => void;
1917
headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>;
2018
handleSearch: (value: string) => void;
19+
isMobileSelectionModeEnabled: boolean;
2120
};
2221

2322
type SearchHeaderOptionValue = DeepValueOf<typeof CONST.SEARCH.BULK_ACTION_TYPES> | undefined;
2423

25-
function SearchPageHeader({queryJSON, searchRouterListVisible, hideSearchRouterList, onSearchRouterFocus, headerButtonsOptions, handleSearch}: SearchPageHeaderProps) {
24+
function SearchPageHeader({
25+
queryJSON,
26+
searchRouterListVisible,
27+
hideSearchRouterList,
28+
onSearchRouterFocus,
29+
headerButtonsOptions,
30+
handleSearch,
31+
isMobileSelectionModeEnabled,
32+
}: SearchPageHeaderProps) {
2633
const {shouldUseNarrowLayout} = useResponsiveLayout();
2734
const {selectedTransactions} = useSearchContext();
28-
const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true});
2935

3036
const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {});
3137

32-
if (shouldUseNarrowLayout && selectionMode?.isEnabled) {
38+
if (shouldUseNarrowLayout && isMobileSelectionModeEnabled) {
3339
return (
3440
<View>
3541
<SearchSelectedNarrow

0 commit comments

Comments
 (0)