Skip to content

Commit 2e4e6c4

Browse files
Reduce mapToTransactionItemWithAdditionalInfo invocations
1 parent f82a8db commit 2e4e6c4

File tree

5 files changed

+67
-34
lines changed

5 files changed

+67
-34
lines changed

Mobile-Expensify

src/components/Search/SearchList/BaseSearchList/index.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function BaseSearchList({
3030
contentContainerStyle,
3131
flattenedItemsLength,
3232
newTransactions,
33+
selectedTransactions,
3334
}: BaseSearchListProps) {
3435
const hasKeyBeenPressed = useRef(false);
3536

@@ -101,7 +102,10 @@ function BaseSearchList({
101102
return () => removeKeyDownPressListener(setHasKeyBeenPressed);
102103
}, [setHasKeyBeenPressed]);
103104

104-
const extraData = useMemo(() => [focusedIndex, isFocused, columns, newTransactions], [focusedIndex, isFocused, columns, newTransactions]);
105+
const extraData = useMemo(
106+
() => [focusedIndex, isFocused, columns, newTransactions, selectedTransactions],
107+
[focusedIndex, isFocused, columns, newTransactions, selectedTransactions],
108+
);
105109

106110
return (
107111
<AnimatedFlashListComponent

src/components/Search/SearchList/BaseSearchList/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {FlashListProps, FlashListRef} from '@shopify/flash-list';
22
import type {ForwardedRef} from 'react';
33
import type {NativeSyntheticEvent} from 'react-native';
4-
import type {SearchColumnType} from '@components/Search/types';
4+
import type {SearchColumnType, SelectedTransactions} from '@components/Search/types';
55
import type {ExtendedTargetedEvent, SearchListItem} from '@components/SelectionListWithSections/types';
66
import type {Transaction} from '@src/types/onyx';
77

@@ -43,6 +43,9 @@ type BaseSearchListProps = Pick<
4343

4444
/** The function to scroll to an index */
4545
scrollToIndex?: (index: number, animated?: boolean) => void;
46+
47+
/** Selected transactions for triggering re-render via extraData */
48+
selectedTransactions?: SelectedTransactions;
4649
};
4750

4851
export default BaseSearchListProps;

src/components/Search/SearchList/index.tsx

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Modal from '@components/Modal';
1515
import {usePersonalDetails} from '@components/OnyxListItemProvider';
1616
import {PressableWithFeedback} from '@components/Pressable';
1717
import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider';
18-
import type {SearchColumnType, SearchGroupBy, SearchQueryJSON} from '@components/Search/types';
18+
import type {SearchColumnType, SearchGroupBy, SearchQueryJSON, SelectedTransactions} from '@components/Search/types';
1919
import type ChatListItem from '@components/SelectionListWithSections/ChatListItem';
2020
import type TaskListItem from '@components/SelectionListWithSections/Search/TaskListItem';
2121
import type TransactionGroupListItem from '@components/SelectionListWithSections/Search/TransactionGroupListItem';
@@ -118,6 +118,9 @@ type SearchListProps = Pick<FlashListProps<SearchListItem>, 'onScroll' | 'conten
118118
/** Callback to fire when DEW modal should be opened */
119119
onDEWModalOpen?: () => void;
120120

121+
/** Selected transactions for determining isSelected state */
122+
selectedTransactions: SelectedTransactions;
123+
121124
/** Reference to the outer element */
122125
ref?: ForwardedRef<SearchListHandle>;
123126
};
@@ -169,6 +172,7 @@ function SearchList({
169172
newTransactions = [],
170173
violations,
171174
onDEWModalOpen,
175+
selectedTransactions,
172176
ref,
173177
}: SearchListProps) {
174178
const styles = useThemeStyles();
@@ -185,13 +189,7 @@ function SearchList({
185189
}, [data, groupBy, type]);
186190
const flattenedItemsWithoutPendingDelete = useMemo(() => flattenedItems.filter((t) => t?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), [flattenedItems]);
187191

188-
const selectedItemsLength = useMemo(
189-
() =>
190-
flattenedItems.reduce((acc, item) => {
191-
return item?.isSelected ? acc + 1 : acc;
192-
}, 0),
193-
[flattenedItems],
194-
);
192+
const selectedItemsLength = useMemo(() => Object.values(selectedTransactions).filter((t) => t?.isSelected).length, [selectedTransactions]);
195193

196194
const {translate} = useLocalize();
197195
const {isOffline} = useNetwork();
@@ -302,6 +300,43 @@ function SearchList({
302300

303301
const newTransactionID = newTransactions.find((transaction) => isTransactionMatchWithGroupItem(transaction, item, groupBy))?.transactionID;
304302

303+
let isSelected = false;
304+
let itemWithSelection: SearchListItem = item;
305+
306+
if ('transactions' in item && item.transactions) {
307+
if (!canSelectMultiple) {
308+
itemWithSelection = {...item, isSelected: false};
309+
} else {
310+
const hasAnySelected = item.transactions.some((t) => t.keyForList && selectedTransactions[t.keyForList]?.isSelected);
311+
312+
if (!hasAnySelected) {
313+
itemWithSelection = {...item, isSelected: false};
314+
} else {
315+
let allNonDeletedSelected = true;
316+
let hasNonDeletedTransactions = false;
317+
318+
const mappedTransactions = item.transactions.map((transaction) => {
319+
const isTransactionSelected = !!(transaction.keyForList && selectedTransactions[transaction.keyForList]?.isSelected);
320+
321+
if (transaction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
322+
hasNonDeletedTransactions = true;
323+
if (!isTransactionSelected) {
324+
allNonDeletedSelected = false;
325+
}
326+
}
327+
328+
return {...transaction, isSelected: isTransactionSelected};
329+
});
330+
331+
isSelected = hasNonDeletedTransactions && allNonDeletedSelected;
332+
itemWithSelection = {...item, isSelected, transactions: mappedTransactions};
333+
}
334+
}
335+
} else {
336+
isSelected = !!(canSelectMultiple && item.keyForList && selectedTransactions[item.keyForList]?.isSelected);
337+
itemWithSelection = {...item, isSelected};
338+
}
339+
305340
return (
306341
<Animated.View
307342
exiting={shouldApplyAnimation && isFocused ? FadeOutUp.duration(CONST.SEARCH.EXITING_ANIMATION_DURATION).easing(easing) : undefined}
@@ -316,7 +351,7 @@ function SearchList({
316351
onLongPressRow={handleLongPressRow}
317352
onCheckboxPress={onCheckboxPress}
318353
canSelectMultiple={canSelectMultiple}
319-
item={item}
354+
item={itemWithSelection}
320355
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
321356
queryJSONHash={hash}
322357
columns={columns}
@@ -354,6 +389,7 @@ function SearchList({
354389
handleLongPressRow,
355390
onCheckboxPress,
356391
canSelectMultiple,
392+
selectedTransactions,
357393
shouldPreventDefaultFocusOnSelectRow,
358394
hash,
359395
columns,
@@ -426,6 +462,7 @@ function SearchList({
426462
onLayout={onLayout}
427463
contentContainerStyle={contentContainerStyle}
428464
newTransactions={newTransactions}
465+
selectedTransactions={selectedTransactions}
429466
/>
430467
<Modal
431468
isVisible={isModalVisible}

src/components/Search/index.tsx

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,11 @@ function mapTransactionItemToSelectedEntry(item: TransactionListItemType, outsta
127127
];
128128
}
129129

130-
function mapToTransactionItemWithAdditionalInfo(
131-
item: TransactionListItemType,
132-
selectedTransactions: SelectedTransactions,
133-
canSelectMultiple: boolean,
134-
shouldAnimateInHighlight: boolean,
135-
hash?: number,
136-
) {
137-
return {...item, shouldAnimateInHighlight, isSelected: selectedTransactions[item.keyForList]?.isSelected && canSelectMultiple, hash};
130+
function mapToTransactionItemWithAdditionalInfo(item: TransactionListItemType, shouldAnimateInHighlight: boolean, hash?: number) {
131+
return {...item, shouldAnimateInHighlight, hash};
138132
}
139133

140-
function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions: SelectedTransactions, canSelectMultiple: boolean, shouldAnimateInHighlight: boolean, hash?: number) {
134+
function mapToItemWithAdditionalInfo(item: SearchListItem, shouldAnimateInHighlight: boolean, hash?: number) {
141135
if (isTaskListItemType(item)) {
142136
return {
143137
...item,
@@ -155,16 +149,10 @@ function mapToItemWithAdditionalInfo(item: SearchListItem, selectedTransactions:
155149
}
156150

157151
return isTransactionListItemType(item)
158-
? mapToTransactionItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash)
152+
? mapToTransactionItemWithAdditionalInfo(item, shouldAnimateInHighlight, hash)
159153
: {
160154
...item,
161155
shouldAnimateInHighlight,
162-
transactions: item.transactions?.map((transaction) =>
163-
mapToTransactionItemWithAdditionalInfo(transaction, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash),
164-
),
165-
isSelected:
166-
item?.transactions?.length > 0 &&
167-
item.transactions?.filter((t) => !isTransactionPendingDelete(t)).every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected && canSelectMultiple),
168156
hash,
169157
};
170158
}
@@ -807,7 +795,7 @@ function Search({
807795
const canSelectMultiple = !isChat && !isTask && (!isSmallScreenWidth || isMobileSelectionModeEnabled) && validGroupBy !== CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID;
808796
const ListItem = getListItem(type, status, validGroupBy);
809797

810-
const sortedSelectedData = useMemo(
798+
const sortedData = useMemo(
811799
() =>
812800
getSortedSections(type, status, filteredData, localeCompare, sortBy, sortOrder, validGroupBy).map((item) => {
813801
const baseKey = isChat
@@ -828,9 +816,9 @@ function Search({
828816
// Determine if either the base key or any transaction key matches
829817
const shouldAnimateInHighlight = isBaseKeyMatch || isAnyTransactionMatch;
830818

831-
return mapToItemWithAdditionalInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight, hash);
819+
return mapToItemWithAdditionalInfo(item, shouldAnimateInHighlight, hash);
832820
}),
833-
[type, status, filteredData, sortBy, sortOrder, validGroupBy, isChat, newSearchResultKeys, selectedTransactions, canSelectMultiple, localeCompare, hash],
821+
[type, status, filteredData, sortBy, sortOrder, validGroupBy, isChat, newSearchResultKeys, localeCompare, hash],
834822
);
835823

836824
useEffect(() => {
@@ -891,8 +879,8 @@ function Search({
891879

892880
const onLayout = useCallback(() => {
893881
endSpan(CONST.TELEMETRY.SPAN_NAVIGATE_TO_REPORTS_TAB);
894-
handleSelectionListScroll(sortedSelectedData, searchListRef.current);
895-
}, [handleSelectionListScroll, sortedSelectedData]);
882+
handleSelectionListScroll(sortedData, searchListRef.current);
883+
}, [handleSelectionListScroll, sortedData]);
896884

897885
const areAllOptionalColumnsHidden = useMemo(() => {
898886
const canBeMissingColumns = expenseHeaders.filter((header) => header.canBeMissing).map((header) => header.columnName);
@@ -972,12 +960,13 @@ function Search({
972960
<Animated.View style={[styles.flex1, animatedStyle]}>
973961
<SearchList
974962
ref={searchListRef}
975-
data={sortedSelectedData}
963+
data={sortedData}
976964
ListItem={ListItem}
977965
onSelectRow={onSelectRow}
978966
onCheckboxPress={toggleTransaction}
979967
onAllCheckboxPress={toggleAllTransactions}
980968
canSelectMultiple={canSelectMultiple}
969+
selectedTransactions={selectedTransactions}
981970
shouldPreventLongPressRow={isChat || isTask}
982971
isFocused={isFocused}
983972
onDEWModalOpen={handleDEWModalOpen}

0 commit comments

Comments
 (0)