Skip to content
47 changes: 35 additions & 12 deletions src/components/ReportActionItem/MoneyRequestReceiptView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import {isReceiptError} from '@libs/ErrorUtils';
import {getMicroSecondOnyxErrorWithTranslationKey, isReceiptError} from '@libs/ErrorUtils';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils';
import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
Expand Down Expand Up @@ -97,7 +97,7 @@ function MoneyRequestReceiptView({
}: MoneyRequestReceiptViewProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {shouldUseNarrowLayout, isInNarrowPaneModal} = useResponsiveLayout();
const {getReportRHPActiveRoute} = useActiveRoute();
const parentReportID = report?.parentReportID;
const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`];
Expand All @@ -108,7 +108,6 @@ function MoneyRequestReceiptView({
});

const [isLoading, setIsLoading] = useState(true);

const parentReportAction = report?.parentReportActionID ? parentReportActions?.[report.parentReportActionID] : undefined;
const {iouReport, chatReport: chatIOUReport, isChatIOUReportArchived} = useGetIOUReportFromReportAction(parentReportAction);
const isTrackExpense = isTrackExpenseReport(report);
Expand Down Expand Up @@ -190,11 +189,15 @@ function MoneyRequestReceiptView({
!isTransactionScanning && (hasReceipt || !!receiptRequiredViolation || !!customRulesViolation) && !!(receiptViolations.length || didReceiptScanSucceed) && isPaidGroupPolicy(report);
const shouldShowReceiptAudit = !isInvoice && (shouldShowReceiptEmptyState || hasReceipt);

const errors = {
...(transaction?.errorFields?.route ?? transaction?.errorFields?.waypoints ?? transaction?.errors),
...parentReportAction?.errors,
};

const errorsWithoutReportCreation = useMemo(
() => ({
...(transaction?.errorFields?.route ?? transaction?.errorFields?.waypoints ?? transaction?.errors),
...parentReportAction?.errors,
}),
[transaction?.errorFields?.route, transaction?.errorFields?.waypoints, transaction?.errors, parentReportAction?.errors],
);
const reportCreationError = useMemo(() => (getCreationReportErrors(report) ? getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage') : {}), [report]);
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ PERF-6 (docs)

Passing the entire report object as a dependency causes this useMemo to re-execute whenever any property of report changes. The function only uses getCreationReportErrors(report), so you should depend on the specific properties that getCreationReportErrors actually checks.

Suggested fix:
If getCreationReportErrors checks specific properties like report.errorFields or similar, use those:

const reportCreationError = useMemo(
    () => (getCreationReportErrors(report) ? getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage') : {}),
    [report?.errorFields, report?.errors], // Adjust based on what getCreationReportErrors actually uses
);

Copy link
Contributor

Choose a reason for hiding this comment

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

Not much we can do about this one as the method expects full report cc @kacper-mikolajczak

const errors = useMemo(() => ({...errorsWithoutReportCreation, ...reportCreationError}), [errorsWithoutReportCreation, reportCreationError]);
const showReceiptErrorWithEmptyState = shouldShowReceiptEmptyState && !hasReceipt && !isEmptyObject(errors);

const [showConfirmDismissReceiptError, setShowConfirmDismissReceiptError] = useState(false);
Expand All @@ -221,10 +224,30 @@ function MoneyRequestReceiptView({
clearAllRelatedReportActionErrors(report.reportID, parentReportAction);
return;
}
revert(transaction, getLastModifiedExpense(report?.reportID));
clearError(transaction.transactionID);
clearAllRelatedReportActionErrors(report.reportID, parentReportAction);
}, [transaction, chatReport, parentReportAction, linkedTransactionID, report?.reportID, iouReport, chatIOUReport, isChatIOUReportArchived]);
if (!isEmptyObject(errorsWithoutReportCreation)) {
revert(transaction, getLastModifiedExpense(report?.reportID));
clearError(transaction.transactionID);
clearAllRelatedReportActionErrors(report.reportID, parentReportAction);
}
if (!isEmptyObject(reportCreationError)) {
if (isInNarrowPaneModal) {
Navigation.goBack();
}
navigateToConciergeChatAndDeleteReport(report.reportID, true, true);
}
}, [
transaction,
chatReport,
parentReportAction,
linkedTransactionID,
report?.reportID,
iouReport,
chatIOUReport,
isChatIOUReportArchived,
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ PERF-6 (docs)

The dismissReceiptError callback depends on entire objects (transaction, chatReport, parentReportAction, iouReport, chatIOUReport), causing it to be recreated whenever any property of these objects changes. This can lead to unnecessary re-renders of child components that receive this callback as a prop.

Suggested fix:
Extract only the specific properties used within the callback:

const dismissReceiptError = useCallback(() => {
    // ... function body
}, [
    transaction?.transactionID,
    transaction?.pendingAction,
    chatReport?.reportID,
    parentReportAction, // May need to break down further if only specific properties are used
    linkedTransactionID,
    report?.reportID,
    iouReport, // May need to break down further
    chatIOUReport, // May need to break down further
    isChatIOUReportArchived,
    errorsWithoutReportCreation,
    reportCreationError,
    isInNarrowPaneModal,
]);

errorsWithoutReportCreation,
reportCreationError,
isInNarrowPaneModal,
]);

let receiptStyle: StyleProp<ViewStyle>;

Expand Down
Loading