Skip to content

Commit 194f7f6

Browse files
authored
Merge pull request Expensify#89083 from software-mansion-labs/perf/split-expense-report-list-item-row-defer-action-cell
perf: Split ExpenseReportListItemRow, defer ActionCell, extract Avatar
2 parents 9592e88 + 2ee9f5e commit 194f7f6

7 files changed

Lines changed: 264 additions & 154 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, {useDeferredValue} from 'react';
2+
import Button from '@components/Button';
3+
import useLocalize from '@hooks/useLocalize';
4+
import useThemeStyles from '@hooks/useThemeStyles';
5+
import CONST from '@src/CONST';
6+
import ActionCell from '.';
7+
import type {ActionCellProps} from '.';
8+
import actionTranslationsMap from './actionTranslationsMap';
9+
10+
function DeferredActionCell(actionCellProps: ActionCellProps) {
11+
const styles = useThemeStyles();
12+
const {translate} = useLocalize();
13+
const shouldRender = useDeferredValue(true, false);
14+
15+
if (!shouldRender) {
16+
const action = actionCellProps.action ?? CONST.SEARCH.ACTION_TYPES.VIEW;
17+
const shouldUseViewAction = action === CONST.SEARCH.ACTION_TYPES.VIEW || action === CONST.SEARCH.ACTION_TYPES.PAID || action === CONST.SEARCH.ACTION_TYPES.DONE;
18+
const isSuccess = !shouldUseViewAction && action !== CONST.SEARCH.ACTION_TYPES.UNDELETE;
19+
const text = shouldUseViewAction ? translate(actionTranslationsMap[CONST.SEARCH.ACTION_TYPES.VIEW]) : translate(actionTranslationsMap[action]);
20+
21+
return (
22+
<Button
23+
text={text}
24+
small={!actionCellProps.extraSmall}
25+
extraSmall={actionCellProps.extraSmall}
26+
style={[styles.w100, styles.pointerEventsNone]}
27+
isDisabled
28+
success={isSuccess}
29+
isNested
30+
/>
31+
);
32+
}
33+
34+
// Deferred wrapper intentionally forwards all props to the underlying component
35+
// eslint-disable-next-line react/jsx-props-no-spreading
36+
return <ActionCell {...actionCellProps} />;
37+
}
38+
39+
export default DeferredActionCell;

src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -309,46 +309,22 @@ function ExpenseReportListItem<TItem extends ListItem>({
309309
isSelected={!!reportItem.isSelected}
310310
/>
311311
)}
312-
{!isLargeScreenWidth && (
313-
<View style={styles.pt3}>
314-
<ExpenseReportListItemRow
315-
item={reportItem}
316-
columns={columns}
317-
reportActions={reportActions}
318-
isActionLoading={isActionLoading ?? isLoading}
319-
showTooltip={showTooltip}
320-
canSelectMultiple={canSelectMultiple}
321-
onCheckboxPress={handleSelectionButtonPress}
322-
onButtonPress={handleOnButtonPress}
323-
isSelectAllChecked={!!reportItem.isSelected}
324-
isIndeterminate={false}
325-
isDisabledCheckbox={isDisabledCheckbox}
326-
isHovered={hovered}
327-
isFocused={isFocused}
328-
isPendingDelete={isPendingDelete}
329-
isLargeScreenWidth={isLargeScreenWidth}
330-
/>
331-
</View>
332-
)}
333-
{isLargeScreenWidth && (
334-
<ExpenseReportListItemRow
335-
item={reportItem}
336-
columns={columns}
337-
reportActions={reportActions}
338-
isActionLoading={isActionLoading ?? isLoading}
339-
showTooltip={showTooltip}
340-
canSelectMultiple={canSelectMultiple}
341-
onCheckboxPress={handleSelectionButtonPress}
342-
onButtonPress={handleOnButtonPress}
343-
isSelectAllChecked={!!reportItem.isSelected}
344-
isIndeterminate={false}
345-
isDisabledCheckbox={isDisabledCheckbox}
346-
isHovered={hovered}
347-
isFocused={isFocused}
348-
isPendingDelete={isPendingDelete}
349-
isLargeScreenWidth={isLargeScreenWidth}
350-
/>
351-
)}
312+
<ExpenseReportListItemRow
313+
item={reportItem}
314+
columns={columns}
315+
reportActions={reportActions}
316+
isActionLoading={isActionLoading ?? isLoading}
317+
showTooltip={showTooltip}
318+
canSelectMultiple={canSelectMultiple}
319+
onCheckboxPress={handleSelectionButtonPress}
320+
onButtonPress={handleOnButtonPress}
321+
isSelectAllChecked={!!reportItem.isSelected}
322+
isIndeterminate={false}
323+
isDisabledCheckbox={isDisabledCheckbox}
324+
isHovered={hovered}
325+
isFocused={isFocused}
326+
isPendingDelete={isPendingDelete}
327+
/>
352328
{getDescription}
353329
</View>
354330
)}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import SearchReportAvatar from '@components/ReportActionAvatars/SearchReportAvatar';
4+
import type {ExpenseReportListItemType} from '@components/Search/SearchList/ListItem/types';
5+
import useStyleUtils from '@hooks/useStyleUtils';
6+
import useTheme from '@hooks/useTheme';
7+
import useThemeStyles from '@hooks/useThemeStyles';
8+
import CONST from '@src/CONST';
9+
10+
type ExpenseReportListItemAvatarProps = {
11+
item: ExpenseReportListItemType;
12+
showTooltip: boolean;
13+
isHovered?: boolean;
14+
isFocused?: boolean;
15+
isLargeScreenWidth?: boolean;
16+
};
17+
18+
function ExpenseReportListItemAvatar({item, showTooltip, isHovered = false, isFocused = false, isLargeScreenWidth = false}: ExpenseReportListItemAvatarProps) {
19+
const StyleUtils = useStyleUtils();
20+
const styles = useThemeStyles();
21+
const theme = useTheme();
22+
23+
const finalAvatarBorderColor =
24+
StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, isFocused || isHovered, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ??
25+
theme.highlightBG;
26+
27+
return (
28+
<View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.AVATAR), styles.alignItemsStretch]}>
29+
<SearchReportAvatar
30+
primaryAvatar={item.primaryAvatar}
31+
secondaryAvatar={item.secondaryAvatar}
32+
avatarType={item.avatarType}
33+
shouldShowTooltip={showTooltip}
34+
subscriptAvatarBorderColor={finalAvatarBorderColor}
35+
reportID={item.reportID}
36+
isLargeScreenWidth={isLargeScreenWidth}
37+
/>
38+
</View>
39+
);
40+
}
41+
42+
export default ExpenseReportListItemAvatar;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import Checkbox from '@components/Checkbox';
4+
import Text from '@components/Text';
5+
import {useCurrencyListActions} from '@hooks/useCurrencyList';
6+
import useLocalize from '@hooks/useLocalize';
7+
import useThemeStyles from '@hooks/useThemeStyles';
8+
import DateUtils from '@libs/DateUtils';
9+
import CONST from '@src/CONST';
10+
import type {ExpenseReportListItemRowNarrowProps} from './types';
11+
12+
function ExpenseReportListItemRowNarrow({item, onCheckboxPress = () => {}, canSelectMultiple, isSelectAllChecked, isIndeterminate, isDisabledCheckbox}: ExpenseReportListItemRowNarrowProps) {
13+
const styles = useThemeStyles();
14+
const {translate} = useLocalize();
15+
const {convertToDisplayString} = useCurrencyListActions();
16+
17+
const currency = item.currency ?? CONST.CURRENCY.USD;
18+
const {totalDisplaySpend = 0, isAllScanning: isScanning = false} = item;
19+
20+
const filteredTransactions = item.transactions?.filter((t) => t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
21+
const expenseCount = (filteredTransactions?.length ? filteredTransactions.length : undefined) ?? item.transactionCount ?? 0;
22+
const expenseCountText = translate('iou.expenseCount', {count: expenseCount});
23+
const formattedDate = DateUtils.formatWithUTCTimeZone(
24+
item.created ?? '',
25+
DateUtils.doesDateBelongToAPastYear(item.created ?? '') ? CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT : CONST.DATE.MONTH_DAY_ABBR_FORMAT,
26+
);
27+
28+
const amountText = isScanning ? translate('iou.receiptStatusTitle') : convertToDisplayString(totalDisplaySpend, currency);
29+
const groupAccessibilityLabel = [item.reportName, amountText, formattedDate, expenseCountText].filter(Boolean).join(', ');
30+
31+
return (
32+
<View
33+
style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, styles.pt3]}
34+
accessible
35+
accessibilityLabel={groupAccessibilityLabel}
36+
role={CONST.ROLE.BUTTON}
37+
>
38+
{!!canSelectMultiple && (
39+
<Checkbox
40+
onPress={onCheckboxPress}
41+
isChecked={isSelectAllChecked}
42+
isIndeterminate={isIndeterminate}
43+
containerStyle={styles.m0}
44+
disabled={isDisabledCheckbox}
45+
accessibilityLabel={item.text ?? ''}
46+
shouldStopMouseDownPropagation
47+
style={[styles.cursorUnset, isDisabledCheckbox && styles.cursorDisabled]}
48+
sentryLabel={CONST.SENTRY_LABEL.SEARCH.EXPENSE_REPORT_CHECKBOX}
49+
/>
50+
)}
51+
<View style={[styles.flexColumn, styles.gap1, styles.flex1]}>
52+
<View style={[styles.flexRow, styles.gap2]}>
53+
<Text
54+
numberOfLines={2}
55+
style={[styles.lh20, styles.flex1]}
56+
>
57+
{item.reportName ?? ''}
58+
</Text>
59+
<Text style={[styles.lh20, styles.flexShrink0, styles.textAlignRight]}>{amountText}</Text>
60+
</View>
61+
<View style={[styles.flexRow, styles.gap2]}>
62+
<Text style={[styles.mutedNormalTextLabel, styles.flex1]}>{formattedDate}</Text>
63+
<Text style={[styles.mutedNormalTextLabel, styles.flexShrink0, styles.textAlignRight]}>{expenseCountText}</Text>
64+
</View>
65+
</View>
66+
</View>
67+
);
68+
}
69+
70+
export default ExpenseReportListItemRowNarrow;

0 commit comments

Comments
 (0)