diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js
index 5d63c176ea6f..3986773aca87 100644
--- a/src/components/LHNOptionsList/LHNOptionsList.js
+++ b/src/components/LHNOptionsList/LHNOptionsList.js
@@ -1,11 +1,20 @@
+import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
-import React from 'react';
+import React, {useCallback} from 'react';
import {FlatList, View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
+import participantPropTypes from '@components/participantPropTypes';
+import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID';
+import compose from '@libs/compose';
+import * as OptionsListUtils from '@libs/OptionsListUtils';
+import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
+import reportPropTypes from '@pages/reportPropTypes';
import styles from '@styles/styles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
-import OptionRowLHNDataWithFocus from './OptionRowLHNDataWithFocus';
+import ONYXKEYS from '@src/ONYXKEYS';
+import OptionRowLHNData from './OptionRowLHNData';
const propTypes = {
/** Wrapper style for the section list */
@@ -27,14 +36,72 @@ const propTypes = {
/** Whether to allow option focus or not */
shouldDisableFocusOptions: PropTypes.bool,
+
+ /** The policy which the user has access to and which the report could be tied to */
+ policy: PropTypes.shape({
+ /** The ID of the policy */
+ id: PropTypes.string,
+ /** Name of the policy */
+ name: PropTypes.string,
+ /** Avatar of the policy */
+ avatar: PropTypes.string,
+ }),
+
+ /** All reports shared with the user */
+ reports: PropTypes.objectOf(reportPropTypes),
+
+ /** Array of report actions for this report */
+ reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
+
+ /** Indicates which locale the user currently has selected */
+ preferredLocale: PropTypes.string,
+
+ /** List of users' personal details */
+ personalDetails: PropTypes.objectOf(participantPropTypes),
+
+ /** The transaction from the parent report action */
+ transactions: PropTypes.objectOf(
+ PropTypes.shape({
+ /** The ID of the transaction */
+ transactionID: PropTypes.string,
+ }),
+ ),
+ /** List of draft comments */
+ draftComments: PropTypes.objectOf(PropTypes.string),
+ ...withCurrentReportIDPropTypes,
};
const defaultProps = {
style: styles.flex1,
shouldDisableFocusOptions: false,
+ reportActions: {},
+ reports: {},
+ policy: {},
+ preferredLocale: CONST.LOCALES.DEFAULT,
+ personalDetails: {},
+ transactions: {},
+ draftComments: {},
+ ...withCurrentReportIDDefaultProps,
};
-function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optionMode, shouldDisableFocusOptions}) {
+const keyExtractor = (item) => item;
+
+function LHNOptionsList({
+ style,
+ contentContainerStyles,
+ data,
+ onSelectRow,
+ optionMode,
+ shouldDisableFocusOptions,
+ reports,
+ reportActions,
+ policy,
+ preferredLocale,
+ personalDetails,
+ transactions,
+ draftComments,
+ currentReportID,
+}) {
/**
* This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization
* so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large
@@ -45,14 +112,17 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
*
* @returns {Object}
*/
- const getItemLayout = (itemData, index) => {
- const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight;
- return {
- length: optionHeight,
- offset: index * optionHeight,
- index,
- };
- };
+ const getItemLayout = useCallback(
+ (itemData, index) => {
+ const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight;
+ return {
+ length: optionHeight,
+ offset: index * optionHeight,
+ index,
+ };
+ },
+ [optionMode],
+ );
/**
* Function which renders a row in the list
@@ -62,13 +132,38 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
*
* @return {Component}
*/
- const renderItem = ({item}) => (
-
+ const renderItem = useCallback(
+ ({item: reportID}) => {
+ const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] || {};
+ const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
+ const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`];
+ const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`];
+ const itemTransaction = `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(
+ itemParentReportActions,
+ [itemFullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'],
+ '',
+ )}`;
+ const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || '';
+ const participantPersonalDetailList = _.values(OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails));
+ return (
+
+ );
+ },
+ [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions],
);
return (
@@ -80,7 +175,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
showsVerticalScrollIndicator={false}
data={data}
testID="lhn-options-list"
- keyExtractor={(item) => item}
+ keyExtractor={keyExtractor}
stickySectionHeadersEnabled={false}
renderItem={renderItem}
getItemLayout={getItemLayout}
@@ -96,4 +191,29 @@ LHNOptionsList.propTypes = propTypes;
LHNOptionsList.defaultProps = defaultProps;
LHNOptionsList.displayName = 'LHNOptionsList';
-export default LHNOptionsList;
+export default compose(
+ withCurrentReportID,
+ withOnyx({
+ reports: {
+ key: ONYXKEYS.COLLECTION.REPORT,
+ },
+ reportActions: {
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ },
+ policy: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
+ preferredLocale: {
+ key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+ },
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ },
+ transactions: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ },
+ draftComments: {
+ key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
+ },
+ }),
+)(LHNOptionsList);
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js
index ebba2ffe0587..4db32ed8bd2a 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.js
+++ b/src/components/LHNOptionsList/OptionRowLHNData.js
@@ -1,19 +1,14 @@
import {deepEqual} from 'fast-equals';
-import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useEffect, useMemo, useRef} from 'react';
-import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import participantPropTypes from '@components/participantPropTypes';
-import compose from '@libs/compose';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import SidebarUtils from '@libs/SidebarUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
-import * as UserUtils from '@libs/UserUtils';
import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import OptionRowLHN, {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './OptionRowLHN';
const propTypes = {
@@ -21,7 +16,7 @@ const propTypes = {
isFocused: PropTypes.bool,
/** List of users' personal details */
- personalDetails: PropTypes.objectOf(participantPropTypes),
+ personalDetails: PropTypes.arrayOf(participantPropTypes),
/** The preferred language for the app */
preferredLocale: PropTypes.string,
@@ -44,10 +39,8 @@ const propTypes = {
parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
/** The transaction from the parent report action */
- transaction: PropTypes.shape({
- /** The ID of the transaction */
- transactionID: PropTypes.string,
- }),
+ transactionID: PropTypes.string,
+
...basePropTypes,
};
@@ -57,7 +50,7 @@ const defaultProps = {
fullReport: {},
policy: {},
parentReportActions: {},
- transaction: {},
+ transactionID: undefined,
preferredLocale: CONST.LOCALES.DEFAULT,
...baseDefaultProps,
};
@@ -78,11 +71,10 @@ function OptionRowLHNData({
policy,
receiptTransactions,
parentReportActions,
- transaction,
+ transactionID,
...propsToForward
}) {
const reportID = propsToForward.reportID;
-
const parentReportAction = parentReportActions[fullReport.parentReportActionID];
const optionItemRef = useRef();
@@ -105,7 +97,7 @@ function OptionRowLHNData({
// Listen parentReportAction to update title of thread report when parentReportAction changed
// Listen to transaction to update title of transaction report when transaction changed
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]);
+ }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transactionID]);
useEffect(() => {
if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) {
@@ -129,30 +121,6 @@ OptionRowLHNData.propTypes = propTypes;
OptionRowLHNData.defaultProps = defaultProps;
OptionRowLHNData.displayName = 'OptionRowLHNData';
-/**
- * @param {Object} [personalDetails]
- * @returns {Object|undefined}
- */
-const personalDetailsSelector = (personalDetails) =>
- _.reduce(
- personalDetails,
- (finalPersonalDetails, personalData, accountID) => {
- // It's OK to do param-reassignment in _.reduce() because we absolutely know the starting state of finalPersonalDetails
- // eslint-disable-next-line no-param-reassign
- finalPersonalDetails[accountID] = {
- accountID: Number(accountID),
- login: personalData.login,
- displayName: personalData.displayName,
- firstName: personalData.firstName,
- status: personalData.status,
- avatar: UserUtils.getAvatar(personalData.avatar, personalData.accountID),
- fallbackIcon: personalData.fallbackIcon,
- };
- return finalPersonalDetails;
- },
- {},
- );
-
/**
* This component is rendered in a list.
* On scroll we want to avoid that a item re-renders
@@ -160,48 +128,4 @@ const personalDetailsSelector = (personalDetails) =>
* Thats also why the React.memo is used on the outer component here, as we just
* use it to prevent re-renders from parent re-renders.
*/
-export default React.memo(
- compose(
- withOnyx({
- comment: {
- key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
- },
- fullReport: {
- key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
- },
- reportActions: {
- key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
- canEvict: false,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- selector: personalDetailsSelector,
- },
- preferredLocale: {
- key: ONYXKEYS.NVP_PREFERRED_LOCALE,
- },
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- parentReportActions: {
- key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`,
- canEvict: false,
- },
- policy: {
- key: ({fullReport}) => `${ONYXKEYS.COLLECTION.POLICY}${fullReport.policyID}`,
- },
- // Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions.
- // In some scenarios, a transaction might be created after reportActions have been modified.
- // This can lead to situations where `lastTransaction` doesn't update and retains the previous value.
- // However, performance overhead of this is minimized by using memos inside the component.
- receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION},
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- transaction: {
- key: ({fullReport, parentReportActions}) =>
- `${ONYXKEYS.COLLECTION.TRANSACTION}${lodashGet(parentReportActions, [fullReport.parentReportActionID, 'originalMessage', 'IOUTransactionID'], '')}`,
- },
- }),
- )(OptionRowLHNData),
-);
+export default React.memo(OptionRowLHNData);
diff --git a/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js b/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js
deleted file mode 100644
index 67e90bcbb0e0..000000000000
--- a/src/components/LHNOptionsList/OptionRowLHNDataWithFocus.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID';
-import OptionRowLHNData from './OptionRowLHNData';
-
-const propTypes = {
- ...withCurrentReportIDPropTypes,
- shouldDisableFocusOptions: PropTypes.bool,
-};
-
-const defaultProps = {
- ...withCurrentReportIDDefaultProps,
- shouldDisableFocusOptions: false,
-};
-
-/**
- * Wrapper component for OptionRowLHNData that calculates isFocused prop based on currentReportID.
- * This is extracted from OptionRowLHNData to prevent unnecessary re-renders when currentReportID changes.
- * @returns {React.Component} OptionRowLHNData component with isFocused prop
- */
-function OptionRowLHNDataWithFocus({currentReportID, shouldDisableFocusOptions, ...props}) {
- // We only want to pass a boolean to the memoized component,
- // instead of a changing number (so we prevent unnecessary re-renders).
- const isFocused = !shouldDisableFocusOptions && currentReportID === props.reportID;
-
- return (
-
- );
-}
-
-OptionRowLHNDataWithFocus.defaultProps = defaultProps;
-OptionRowLHNDataWithFocus.propTypes = propTypes;
-OptionRowLHNDataWithFocus.displayName = 'OptionRowLHNDataWithFocus';
-
-export default withCurrentReportID(OptionRowLHNDataWithFocus);