diff --git a/src/App.tsx b/src/App.tsx index 6316fa80fba1..a3a9f7a3f3b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -32,7 +32,6 @@ import {KeyboardStateProvider} from './components/withKeyboardState'; import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; -import {ReportIDsContextProvider} from './hooks/useReportIDs'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; @@ -79,7 +78,6 @@ function App({url}: AppProps) { CustomStatusBarAndBackgroundContextProvider, ActiveElementRoleProvider, ActiveWorkspaceContextProvider, - ReportIDsContextProvider, PlaybackContextProvider, FullScreenContextProvider, VolumeContextProvider, diff --git a/src/components/withCurrentReportID.tsx b/src/components/withCurrentReportID.tsx index a72063913283..a452e7565b4e 100644 --- a/src/components/withCurrentReportID.tsx +++ b/src/components/withCurrentReportID.tsx @@ -39,17 +39,7 @@ function CurrentReportIDContextProvider(props: CurrentReportIDContextProviderPro */ const updateCurrentReportID = useCallback( (state: NavigationState) => { - const reportID = Navigation.getTopmostReportId(state) ?? ''; - - /* - * Make sure we don't make the reportID undefined when switching between the chat list and settings tab. - * This helps prevent unnecessary re-renders. - */ - const params = state?.routes?.[state.index]?.params; - if (params && 'screen' in params && typeof params.screen === 'string' && params.screen.indexOf('Settings_') !== -1) { - return; - } - setCurrentReportID(reportID); + setCurrentReportID(Navigation.getTopmostReportId(state) ?? ''); }, [setCurrentReportID], ); diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx deleted file mode 100644 index 58d4e42cd83b..000000000000 --- a/src/hooks/useReportIDs.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, {createContext, useCallback, useContext, useMemo} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; -import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {Message} from '@src/types/onyx/ReportAction'; -import useActiveWorkspace from './useActiveWorkspace'; -import useCurrentReportID from './useCurrentReportID'; -import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; - -type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; -type PolicySelector = Pick; -type ReportActionsSelector = Array>; - -type ReportIDsContextProviderProps = { - children: React.ReactNode; - currentReportIDForTests?: string; -}; - -type ReportIDsContextValue = { - orderedReportIDs: string[]; - currentReportID: string; -}; - -const ReportIDsContext = createContext({ - orderedReportIDs: [], - currentReportID: '', -}); - -/** - * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering - * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. - */ -const chatReportSelector = (report: OnyxEntry): ChatReportSelector => - (report && { - reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, - isPinned: report.isPinned, - isHidden: report.isHidden, - notificationPreference: report.notificationPreference, - errorFields: { - addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, - }, - lastMessageText: report.lastMessageText, - lastVisibleActionCreated: report.lastVisibleActionCreated, - iouReportID: report.iouReportID, - total: report.total, - nonReimbursableTotal: report.nonReimbursableTotal, - hasOutstandingChildRequest: report.hasOutstandingChildRequest, - isWaitingOnBankAccount: report.isWaitingOnBankAccount, - statusNum: report.statusNum, - stateNum: report.stateNum, - chatType: report.chatType, - type: report.type, - policyID: report.policyID, - visibility: report.visibility, - lastReadTime: report.lastReadTime, - // Needed for name sorting: - reportName: report.reportName, - policyName: report.policyName, - oldPolicyName: report.oldPolicyName, - // Other less obvious properites considered for sorting: - ownerAccountID: report.ownerAccountID, - currency: report.currency, - managerID: report.managerID, - // Other important less obivous properties for filtering: - parentReportActionID: report.parentReportActionID, - parentReportID: report.parentReportID, - isDeletedParentAction: report.isDeletedParentAction, - isUnreadWithMention: ReportUtils.isUnreadWithMention(report), - }) as ChatReportSelector; - -const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => - (reportActions && - Object.values(reportActions).map((reportAction) => { - const {reportActionID, actionName, errors = [], originalMessage} = reportAction; - const decision = reportAction.message?.[0]?.moderationDecision?.decision; - - return { - reportActionID, - actionName, - errors, - message: [ - { - moderationDecision: {decision}, - }, - ] as Message[], - originalMessage, - }; - })) as ReportActionsSelector; - -const policySelector = (policy: OnyxEntry): PolicySelector => - (policy && { - type: policy.type, - name: policy.name, - avatar: policy.avatar, - employeeList: policy.employeeList, - }) as PolicySelector; - -function ReportIDsContextProvider({ - children, - /** - * Only required to make unit tests work, since we - * explicitly pass the currentReportID in LHNTestUtils - * to SidebarLinksData, so this context doesn't have - * access to currentReportID in that case. - * - * This is a workaround to have currentReportID available in testing environment. - */ - currentReportIDForTests, -}: ReportIDsContextProviderProps) { - const [priorityMode] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {initialValue: CONST.PRIORITY_MODE.DEFAULT}); - const [chatReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {selector: chatReportSelector}); - const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: policySelector}); - const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {selector: reportActionsSelector}); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); - const [betas] = useOnyx(ONYXKEYS.BETAS); - - const {accountID} = useCurrentUserPersonalDetails(); - const currentReportIDValue = useCurrentReportID(); - const derivedCurrentReportID = currentReportIDForTests ?? currentReportIDValue?.currentReportID; - const {activeWorkspaceID} = useActiveWorkspace(); - - const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID); - - const getOrderedReportIDs = useCallback( - (currentReportID?: string) => - SidebarUtils.getOrderedReportIDs( - currentReportID ?? null, - chatReports, - betas, - policies, - priorityMode, - allReportActions, - transactionViolations, - activeWorkspaceID, - policyMemberAccountIDs, - ), - // we need reports draft in deps array for reloading of list when reportsDrafts will change - // eslint-disable-next-line react-hooks/exhaustive-deps - [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, reportsDrafts], - ); - - const orderedReportIDs = useMemo(() => getOrderedReportIDs(), [getOrderedReportIDs]); - const contextValue: ReportIDsContextValue = useMemo(() => { - // We need to make sure the current report is in the list of reports, but we do not want - // to have to re-generate the list every time the currentReportID changes. To do that - // we first generate the list as if there was no current report, then we check if - // the current report is missing from the list, which should very rarely happen. In this - // case we re-generate the list a 2nd time with the current report included. - if (derivedCurrentReportID && !orderedReportIDs.includes(derivedCurrentReportID)) { - return {orderedReportIDs: getOrderedReportIDs(derivedCurrentReportID), currentReportID: derivedCurrentReportID ?? ''}; - } - - return { - orderedReportIDs, - currentReportID: derivedCurrentReportID ?? '', - }; - }, [getOrderedReportIDs, orderedReportIDs, derivedCurrentReportID]); - - return {children}; -} - -function useReportIDs() { - return useContext(ReportIDsContext); -} - -export {ReportIDsContext, ReportIDsContextProvider, policySelector, useReportIDs}; -export type {ChatReportSelector, PolicySelector, ReportActionsSelector}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 4f1a35ee1d87..a218534e6b16 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,14 +1,14 @@ import Str from 'expensify-common/lib/str'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {ChatReportSelector, PolicySelector, ReportActionsSelector} from '@hooks/useReportIDs'; +import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {PersonalDetails, PersonalDetailsList, ReportActions, TransactionViolation} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx'; import type Beta from '@src/types/onyx/Beta'; import type Policy from '@src/types/onyx/Policy'; -import type PriorityMode from '@src/types/onyx/PriorityMode'; import type Report from '@src/types/onyx/Report'; +import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; @@ -62,11 +62,11 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { */ function getOrderedReportIDs( currentReportId: string | null, - allReports: OnyxCollection, + allReports: OnyxCollection, betas: OnyxEntry, - policies: OnyxCollection, - priorityMode: OnyxEntry, - allReportActions: OnyxCollection, + policies: OnyxCollection, + priorityMode: OnyxEntry>, + allReportActions: OnyxCollection, transactionViolations: OnyxCollection, currentPolicyID = '', policyMemberAccountIDs: number[] = [], @@ -88,7 +88,7 @@ function getOrderedReportIDs( const doesReportHaveViolations = !!( betas?.includes(CONST.BETAS.VIOLATIONS) && !!parentReportAction && - ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction as OnyxEntry) + ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction) ); const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; @@ -104,7 +104,7 @@ function getOrderedReportIDs( currentReportId: currentReportId ?? '', isInGSDMode, betas, - policies: policies as OnyxCollection, + policies, excludeEmptyChats: true, doesReportHaveViolations, includeSelfDM: true, @@ -131,13 +131,13 @@ function getOrderedReportIDs( ); } // There are a few properties that need to be calculated for the report which are used when sorting reports. - reportsToDisplay.forEach((reportToDisplay) => { - let report = reportToDisplay as OnyxEntry; + reportsToDisplay.forEach((report) => { + // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. + // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add + // the reportDisplayName property to the report object directly. if (report) { - report = { - ...report, - displayName: ReportUtils.getReportName(report), - }; + // eslint-disable-next-line no-param-reassign + report.displayName = ReportUtils.getReportName(report); } const isPinned = report?.isPinned ?? false; diff --git a/src/pages/home/sidebar/SidebarLinksData.tsx b/src/pages/home/sidebar/SidebarLinksData.tsx index 46f7d2410ffe..1000ceff1a76 100644 --- a/src/pages/home/sidebar/SidebarLinksData.tsx +++ b/src/pages/home/sidebar/SidebarLinksData.tsx @@ -1,56 +1,152 @@ import {useIsFocused} from '@react-navigation/native'; +import {deepEqual} from 'fast-equals'; import lodashIsEqual from 'lodash/isEqual'; -import React, {memo, useCallback, useEffect, useRef} from 'react'; +import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {EdgeInsets} from 'react-native-safe-area-context'; import type {ValueOf} from 'type-fest'; +import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import withCurrentReportID from '@components/withCurrentReportID'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; -import type {PolicySelector} from '@hooks/useReportIDs'; -import {policySelector, useReportIDs} from '@hooks/useReportIDs'; +import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import SidebarUtils from '@libs/SidebarUtils'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; import SidebarLinks from './SidebarLinks'; +type ChatReportSelector = OnyxTypes.Report & {isUnreadWithMention: boolean}; +type PolicySelector = Pick; +type ReportActionsSelector = Array>; + type SidebarLinksDataOnyxProps = { + /** List of reports */ + chatReports: OnyxCollection; + /** Whether the reports are loading. When false it means they are ready to be used. */ isLoadingApp: OnyxEntry; /** The chat priority mode */ priorityMode: OnyxEntry>; + /** Beta features list */ + betas: OnyxEntry; + + /** All report actions for all reports */ + allReportActions: OnyxCollection; + /** The policies which the user has access to */ policies: OnyxCollection; -}; -type SidebarLinksDataProps = SidebarLinksDataOnyxProps & { - /** Toggles the navigation menu open and closed */ - onLinkClick: () => void; + /** All of the transaction violations */ + transactionViolations: OnyxCollection; - /** Safe area insets required for mobile devices margins */ - insets: EdgeInsets; + /** Drafts of reports */ + reportsDrafts: OnyxCollection; }; -function SidebarLinksData({insets, isLoadingApp = true, onLinkClick, priorityMode = CONST.PRIORITY_MODE.DEFAULT, policies}: SidebarLinksDataProps) { +type SidebarLinksDataProps = CurrentReportIDContextValue & + SidebarLinksDataOnyxProps & { + /** Toggles the navigation menu open and closed */ + onLinkClick: () => void; + + /** Safe area insets required for mobile devices margins */ + insets: EdgeInsets; + }; + +function SidebarLinksData({ + allReportActions, + betas, + chatReports, + currentReportID, + insets, + isLoadingApp = true, + onLinkClick, + policies, + priorityMode = CONST.PRIORITY_MODE.DEFAULT, + transactionViolations, + reportsDrafts, +}: SidebarLinksDataProps) { const {accountID} = useCurrentUserPersonalDetails(); + const network = useNetwork(); const isFocused = useIsFocused(); const styles = useThemeStyles(); const {activeWorkspaceID} = useActiveWorkspace(); const {translate} = useLocalize(); + const prevPriorityMode = usePrevious(priorityMode); const policyMemberAccountIDs = getPolicyEmployeeListByIdWithoutCurrentUser(policies, activeWorkspaceID, accountID); // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => Policy.openWorkspace(activeWorkspaceID ?? '', policyMemberAccountIDs), [activeWorkspaceID]); + const reportIDsRef = useRef(null); const isLoading = isLoadingApp; - const {orderedReportIDs, currentReportID} = useReportIDs(); + + const optionItemsMemoized: string[] = useMemo( + () => + SidebarUtils.getOrderedReportIDs( + null, + chatReports, + betas, + policies as OnyxCollection, + priorityMode, + allReportActions as OnyxCollection, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ), + // we need reports draft in deps array for reloading of list when reportDrafts will change + // eslint-disable-next-line react-hooks/exhaustive-deps + [chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs, reportsDrafts], + ); + + const optionListItems: string[] | null = useMemo(() => { + const reportIDs = optionItemsMemoized; + + if (deepEqual(reportIDsRef.current, reportIDs)) { + return reportIDsRef.current; + } + + // 1. We need to update existing reports only once while loading because they are updated several times during loading and causes this regression: https://github.com/Expensify/App/issues/24596#issuecomment-1681679531 + // 2. If the user is offline, we need to update the reports unconditionally, since the loading of report data might be stuck in this case. + // 3. Changing priority mode to Most Recent will call OpenApp. If there is an existing reports and the priority mode is updated, we want to immediately update the list instead of waiting the OpenApp request to complete + if (!isLoading || !reportIDsRef.current || network.isOffline || (reportIDsRef.current && prevPriorityMode !== priorityMode)) { + reportIDsRef.current = reportIDs; + } + return reportIDsRef.current ?? []; + }, [optionItemsMemoized, priorityMode, isLoading, network.isOffline, prevPriorityMode]); + // We need to make sure the current report is in the list of reports, but we do not want + // to have to re-generate the list every time the currentReportID changes. To do that + // we first generate the list as if there was no current report, then here we check if + // the current report is missing from the list, which should very rarely happen. In this + // case we re-generate the list a 2nd time with the current report included. + const optionListItemsWithCurrentReport = useMemo(() => { + if (currentReportID && !optionListItems?.includes(currentReportID)) { + return SidebarUtils.getOrderedReportIDs( + currentReportID, + chatReports as OnyxCollection, + betas, + policies as OnyxCollection, + priorityMode, + allReportActions as OnyxCollection, + transactionViolations, + activeWorkspaceID, + policyMemberAccountIDs, + ); + } + return optionListItems ?? []; + }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations, activeWorkspaceID, policyMemberAccountIDs]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -71,8 +167,8 @@ function SidebarLinksData({insets, isLoadingApp = true, onLinkClick, priorityMod // Data props: isActiveReport={isActiveReport} isLoading={isLoading ?? false} + optionListItems={optionListItemsWithCurrentReport} activeWorkspaceID={activeWorkspaceID} - optionListItems={orderedReportIDs} /> ); @@ -80,7 +176,105 @@ function SidebarLinksData({insets, isLoadingApp = true, onLinkClick, priorityMod SidebarLinksData.displayName = 'SidebarLinksData'; -export default withOnyx({ +/** + * This function (and the few below it), narrow down the data from Onyx to just the properties that we want to trigger a re-render of the component. This helps minimize re-rendering + * and makes the entire component more performant because it's not re-rendering when a bunch of properties change which aren't ever used in the UI. + */ +const chatReportSelector = (report: OnyxEntry): ChatReportSelector => + (report && { + reportID: report.reportID, + participantAccountIDs: report.participantAccountIDs, + isPinned: report.isPinned, + isHidden: report.isHidden, + notificationPreference: report.notificationPreference, + errorFields: { + addWorkspaceRoom: report.errorFields?.addWorkspaceRoom, + }, + lastMessageText: report.lastMessageText, + lastVisibleActionCreated: report.lastVisibleActionCreated, + iouReportID: report.iouReportID, + total: report.total, + nonReimbursableTotal: report.nonReimbursableTotal, + hasOutstandingChildRequest: report.hasOutstandingChildRequest, + isWaitingOnBankAccount: report.isWaitingOnBankAccount, + statusNum: report.statusNum, + stateNum: report.stateNum, + chatType: report.chatType, + type: report.type, + policyID: report.policyID, + visibility: report.visibility, + lastReadTime: report.lastReadTime, + // Needed for name sorting: + reportName: report.reportName, + policyName: report.policyName, + oldPolicyName: report.oldPolicyName, + // Other less obvious properites considered for sorting: + ownerAccountID: report.ownerAccountID, + currency: report.currency, + managerID: report.managerID, + // Other important less obivous properties for filtering: + parentReportActionID: report.parentReportActionID, + parentReportID: report.parentReportID, + isDeletedParentAction: report.isDeletedParentAction, + isUnreadWithMention: ReportUtils.isUnreadWithMention(report), + }) as ChatReportSelector; + +const reportActionsSelector = (reportActions: OnyxEntry): ReportActionsSelector => + (reportActions && + Object.values(reportActions).map((reportAction) => { + const {reportActionID, actionName, errors = [], originalMessage} = reportAction; + const decision = reportAction.message?.[0]?.moderationDecision?.decision; + + return { + reportActionID, + actionName, + errors, + message: [ + { + moderationDecision: {decision}, + }, + ] as Message[], + originalMessage, + }; + })) as ReportActionsSelector; + +const policySelector = (policy: OnyxEntry): PolicySelector => + (policy && { + type: policy.type, + name: policy.name, + avatar: policy.avatar, + employeeList: policy.employeeList, + }) as PolicySelector; + +const SidebarLinkDataWithCurrentReportID = withCurrentReportID( + /* + While working on audit on the App Start App metric we noticed that by memoizing SidebarLinksData we can avoid 2 additional run of getOrderedReportIDs. + With that we can reduce app start up time by ~2s on heavy account. + More details - https://github.com/Expensify/App/issues/35234#issuecomment-1926914534 + */ + memo( + SidebarLinksData, + (prevProps, nextProps) => + lodashIsEqual(prevProps.chatReports, nextProps.chatReports) && + lodashIsEqual(prevProps.allReportActions, nextProps.allReportActions) && + prevProps.isLoadingApp === nextProps.isLoadingApp && + prevProps.priorityMode === nextProps.priorityMode && + lodashIsEqual(prevProps.betas, nextProps.betas) && + lodashIsEqual(prevProps.policies, nextProps.policies) && + lodashIsEqual(prevProps.insets, nextProps.insets) && + prevProps.onLinkClick === nextProps.onLinkClick && + lodashIsEqual(prevProps.transactionViolations, nextProps.transactionViolations) && + prevProps.currentReportID === nextProps.currentReportID && + lodashIsEqual(prevProps.reportsDrafts, nextProps.reportsDrafts), + ), +); + +export default withOnyx, SidebarLinksDataOnyxProps>({ + chatReports: { + key: ONYXKEYS.COLLECTION.REPORT, + selector: chatReportSelector, + initialValue: {}, + }, isLoadingApp: { key: ONYXKEYS.IS_LOADING_APP, }, @@ -88,24 +282,26 @@ export default withOnyx({ key: ONYXKEYS.NVP_PRIORITY_MODE, initialValue: CONST.PRIORITY_MODE.DEFAULT, }, + betas: { + key: ONYXKEYS.BETAS, + initialValue: [], + }, + allReportActions: { + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + selector: reportActionsSelector, + initialValue: {}, + }, policies: { key: ONYXKEYS.COLLECTION.POLICY, selector: policySelector, initialValue: {}, }, -})( - /* -While working on audit on the App Start App metric we noticed that by memoizing SidebarLinksData we can avoid 2 additional run of getOrderedReportIDs. -With that we can reduce app start up time by ~2s on heavy account. -More details - https://github.com/Expensify/App/issues/35234#issuecomment-1926914534 -*/ - memo( - SidebarLinksData, - (prevProps, nextProps) => - prevProps.isLoadingApp === nextProps.isLoadingApp && - prevProps.priorityMode === nextProps.priorityMode && - lodashIsEqual(prevProps.insets, nextProps.insets) && - prevProps.onLinkClick === nextProps.onLinkClick && - lodashIsEqual(prevProps.policies, nextProps.policies), - ), -); + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, + reportsDrafts: { + key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, + initialValue: {}, + }, +})(SidebarLinkDataWithCurrentReportID); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index f0cb062fb4b3..6e5640d21ff9 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -115,8 +115,6 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi throw new Error('Not implemented'); } -const stickyHeaderIndices = [0]; - function WorkspacesListPage({policies, reimbursementAccount, reports, session}: WorkspaceListPageProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -424,7 +422,7 @@ function WorkspacesListPage({policies, reimbursementAccount, reports, session}: data={workspaces} renderItem={getMenuItem} ListHeaderComponent={listHeaderComponent} - stickyHeaderIndices={stickyHeaderIndices} + stickyHeaderIndices={[0]} /> ( +const allReports = createCollection( (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, (index) => ({ ...createRandomReport(index), @@ -29,7 +29,6 @@ const allReports = createCollection( // add status and state to every 5th report to mock nonarchived reports statusNum: index % REPORT_TRESHOLD ? 0 : CONST.REPORT.STATUS_NUM.CLOSED, stateNum: index % REPORT_TRESHOLD ? 0 : CONST.REPORT.STATE_NUM.APPROVED, - isUnreadWithMention: false, }), REPORTS_COUNT, ); diff --git a/tests/unit/SidebarOrderTest.ts b/tests/unit/SidebarOrderTest.ts index fd39d4efef65..868630a8f7d2 100644 --- a/tests/unit/SidebarOrderTest.ts +++ b/tests/unit/SidebarOrderTest.ts @@ -299,7 +299,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...reportCollectionDataSet, }), ) @@ -363,7 +363,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, ...reportCollectionDataSet, }), ) @@ -430,7 +430,7 @@ describe('Sidebar', () => { Onyx.multiSet({ [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.IS_LOADING_APP]: false, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.POLICY}${fakeReport.policyID}`]: fakePolicy, ...reportCollectionDataSet, }), @@ -785,12 +785,10 @@ describe('Sidebar', () => { // When a new report is added .then(() => - Onyx.multiSet({ - ...reportDraftCommentCollectionDataSet, - [`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report4.reportID}`]: 'report4 draft', - ...reportCollectionDataSet, - [`${ONYXKEYS.COLLECTION.REPORT}${report4.reportID}`]: report4, - }), + Promise.all([ + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${report4.reportID}`, report4), + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report4.reportID}`, 'report4 draft'), + ]), ) // Then they are still in alphabetical order diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index e3daa93a3179..248b0f946edc 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -8,7 +8,6 @@ import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxProvider from '@components/OnyxProvider'; import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; -import {ReportIDsContextProvider} from '@hooks/useReportIDs'; import DateUtils from '@libs/DateUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; @@ -279,26 +278,17 @@ function getFakeAdvancedReportAction(actionName: ActionName = 'IOU', actor = 'em function MockedSidebarLinks({currentReportID = ''}: MockedSidebarLinksProps) { return ( - {/* - * Only required to make unit tests work, since we - * explicitly pass the currentReportID in LHNTestUtils - * to SidebarLinksData, so this context doesn't have an - * access to currentReportID in that case. - * - * So this is a work around to have currentReportID available - * only in testing environment. - * */} - - {}} - insets={{ - top: 0, - left: 0, - right: 0, - bottom: 0, - }} - /> - + {}} + insets={{ + top: 0, + left: 0, + right: 0, + bottom: 0, + }} + // @ts-expect-error - we need this prop to be able to test the component but normally its provided by HOC + currentReportID={currentReportID} + /> ); }