diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 633dca077070..182b7dc286ef 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -79,11 +79,11 @@ function ReportActionItemImages({images, size, total, isHovered = false, onPress const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; return ( - - + + getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID), }); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); - const wasLoadingApp = usePrevious(isLoadingApp); - const finishedLoadingApp = wasLoadingApp && !isLoadingApp; const deletedParentAction = isDeletedParentAction(parentReportAction); const prevDeletedParentAction = usePrevious(deletedParentAction); - const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); const permissions = useDeepCompareRef(reportOnyx?.permissions); useEffect(() => { @@ -194,7 +180,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { Log.info(`[ReportScreen] no reportID found in params, setting it to lastAccessedReportID: ${lastAccessedReportID}`); navigation.setParams({reportID: lastAccessedReportID}); - }, [activeWorkspaceID, canUseDefaultRooms, navigation, route, finishedLoadingApp]); + }, [activeWorkspaceID, canUseDefaultRooms, navigation, route]); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const chatWithAccountManagerText = useMemo(() => { @@ -272,6 +258,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [isLinkingToMessage, setIsLinkingToMessage] = useState(!!reportActionIDFromRoute); const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: (value) => value?.accountID}); + const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const {reportActions, linkedAction, sortedAllReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID, reportActionIDFromRoute); const [isBannerVisible, setIsBannerVisible] = useState(true); @@ -291,7 +278,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { [reportActions, reportActionIDFromRoute], ); - const isPendingActionExist = !!reportActions.at(0)?.pendingAction; const doesCreatedActionExists = useCallback(() => !!reportActions?.findLast((action) => isCreatedAction(action)), [reportActions]); const isLinkedMessageAvailable = indexOfLinkedMessage > -1; @@ -300,10 +286,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // OpenReport will be called each time the user scrolls up the report a bit, clicks on report preview, and then goes back." const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); - // If there's a non-404 error for the report we should show it instead of blocking the screen - const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); - const shouldHideReport = !hasHelpfulErrors && !canAccessReport(report, policies, betas); - const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], isOffline); const [transactionThreadReportActions = {}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`); const combinedReportActions = getCombinedReportActions(reportActions, transactionThreadReportID ?? null, Object.values(transactionThreadReportActions)); @@ -322,12 +304,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }, [prevIsFocused, isFocused]); useEffect(() => { - if (!report?.reportID || shouldHideReport) { + if (!report?.reportID) { wasReportAccessibleRef.current = false; return; } wasReportAccessibleRef.current = true; - }, [shouldHideReport, report]); + }, [report]); const onBackButtonPress = useCallback(() => { if (isInNarrowPaneModal) { @@ -386,10 +368,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return reportIDFromRoute !== '' && !!report?.reportID && !isTransitioning; }, [report, reportIDFromRoute]); - const isInitialPageReady = isOffline - ? reportActions.length > 0 - : reportActions.length >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); - const isLinkedActionDeleted = useMemo( () => !!linkedAction && !shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, canUserPerformWriteAction(report)), [linkedAction, report], @@ -420,22 +398,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }); }, [isFocused, deleteTransactionNavigateBackUrl]); - const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || isPersonalDetailsEmpty()); - - const shouldShowSkeleton = - (isLinkingToMessage && !isLinkedMessagePageReady) || - (!isLinkingToMessage && !isInitialPageReady) || - isEmptyObject(reportOnyx) || - isLoadingReportOnyx || - !isCurrentReportLoadedFromOnyx || - (!!deleteTransactionNavigateBackUrl && getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || - (!reportMetadata.isOptimisticReport && isLoading); - // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundLinkedAction = (!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted && isNavigatingToDeletedAction) || - (shouldShowSkeleton && - !reportMetadata.isLoadingInitialReportActions && + (!reportMetadata?.isLoadingInitialReportActions && !!reportActionIDFromRoute && !!sortedAllReportActions && sortedAllReportActions?.length > 0 && @@ -451,41 +417,20 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return true; } - // Wait until we're sure the app is done loading (needs to be a strict equality check since it's undefined initially) if (isLoadingApp !== false) { return false; } - // If we just finished loading the app, we still need to try fetching the report. Wait until that's done before - // showing the Not Found page - if (finishedLoadingApp) { - return false; - } - // eslint-disable-next-line react-compiler/react-compiler if (!wasReportAccessibleRef.current && !firstRenderRef.current && !reportID && !isOptimisticDelete && !reportMetadata?.isLoadingInitialReportActions && !userLeavingStatus) { // eslint-disable-next-line react-compiler/react-compiler return true; } - if (shouldHideReport) { - return true; - } return !!currentReportIDFormRoute && !isValidReportIDFromPath(currentReportIDFormRoute); }, // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - [ - firstRender, - shouldShowNotFoundLinkedAction, - isLoadingApp, - finishedLoadingApp, - reportID, - isOptimisticDelete, - reportMetadata?.isLoadingInitialReportActions, - userLeavingStatus, - shouldHideReport, - currentReportIDFormRoute, - ], + [firstRender, shouldShowNotFoundLinkedAction, reportID, isOptimisticDelete, reportMetadata?.isLoadingInitialReportActions, userLeavingStatus, currentReportIDFormRoute], ); const fetchReport = useCallback(() => { @@ -512,38 +457,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }; }, [reportID]); - const fetchReportIfNeeded = useCallback(() => { - // Report ID will be empty when the reports collection is empty. - // This could happen when we are loading the collection for the first time after logging in. - if (!isValidReportIDFromPath(reportIDFromRoute)) { - return; - } - - /** - * Since OpenReport is a write, the response from OpenReport will get dropped while the app is - * still loading. This usually happens when signing in and deeplinking to a report. Instead, - * we'll fetch the report after the app finishes loading. - * - * This needs to be a strict equality check since isLoadingApp is initially undefined until the - * value is loaded from Onyx - */ - if (isLoadingApp !== false) { - return; - } - - if (!shouldFetchReport(report, reportMetadata.isOptimisticReport)) { - return; - } - // When creating an optimistic report that already exists, we need to skip openReport - // when replacing the optimistic report with the real one received from the server. - if (isSkippingOpenReport.current) { - isSkippingOpenReport.current = false; - return; - } - - fetchReport(); - }, [reportIDFromRoute, isLoadingApp, report, fetchReport, reportMetadata.isOptimisticReport]); - const dismissBanner = useCallback(() => { setIsBannerVisible(false); }, []); @@ -583,25 +496,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }, []); useEffect(() => { - // Call OpenReport only if we are not linking to a message or the report is not available yet - if (isLoadingReportOnyx || reportActionIDFromRoute) { - return; - } - fetchReportIfNeeded(); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [isLoadingReportOnyx]); - - useEffect(() => { - if (isLoadingReportOnyx || !reportActionIDFromRoute || isLinkedMessagePageReady) { - return; - } - // This function is triggered when a user clicks on a link to navigate to a report. // For each link click, we retrieve the report data again, even though it may already be cached. // There should be only one openReport execution per page start or navigating fetchReport(); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [route, isLinkedMessagePageReady, isLoadingReportOnyx, reportActionIDFromRoute]); + }, [route, isLinkedMessagePageReady, reportActionIDFromRoute]); // If a user has chosen to leave a thread, and then returns to it (e.g. with the back button), we need to call `openReport` again in order to allow the user to rejoin and to receive real-time updates useEffect(() => { @@ -671,14 +571,11 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return; } - fetchReportIfNeeded(); setShouldShowComposeInput(true); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ route, report, - // errors, - fetchReportIfNeeded, prevReport?.reportID, prevUserLeavingStatus, userLeavingStatus, @@ -730,18 +627,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }); }, [reportMetadata?.isLoadingInitialReportActions]); - // If we deeplinked to the report after signing in, we need to fetch the report after the app is done loading - useEffect(() => { - if (!finishedLoadingApp) { - return; - } - - fetchReportIfNeeded(); - - // This should only run once when the app is done loading - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [finishedLoadingApp]); - const navigateToEndOfReport = useCallback(() => { Navigation.setParams({reportActionID: ''}); fetchReport(); @@ -784,18 +669,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. readNewestAction(report?.reportID); }, [report]); - const mostRecentReportAction = reportActions.at(0); - const isMostRecentReportIOU = mostRecentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; - const isSingleIOUReportAction = reportActions.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU).length === 1; - const isSingleExpenseReport = isExpenseReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; - const isSingleInvoiceReport = isInvoiceReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; - const shouldShowMostRecentReportAction = - !!mostRecentReportAction && - shouldReportActionBeVisible(mostRecentReportAction, mostRecentReportAction.reportActionID, canUserPerformWriteAction(report)) && - !isSingleExpenseReport && - !isSingleInvoiceReport && - !isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && - !isDeletedAction(mostRecentReportAction); const lastRoute = usePrevious(route); @@ -855,46 +728,18 @@ function ReportScreen({route, navigation}: ReportScreenProps) { style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]} testID="report-actions-view-wrapper" > - {!shouldShowSkeleton && !!report && ( + {!!report && !isLoadingApp ? ( - )} - - {/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded. - If we prevent rendering the report while they are loading then - we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */} - {shouldShowSkeleton && ( - <> - - {shouldShowMostRecentReportAction && ( - - )} - + ) : ( + )} {isCurrentReportLoadedFromOnyx ? ( diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index c545ba590848..a37a28a7ee0a 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -3,7 +3,7 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; // eslint-disable-next-line lodash/import-scope import type {DebouncedFunc} from 'lodash'; import React, {memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; -import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; +import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'; import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; @@ -88,21 +88,6 @@ type ReportActionsListProps = { /** The ID of the most recent IOU report action connected with the shown report */ mostRecentIOUReportActionID?: string | null; - /** The report metadata loading states */ - isLoadingInitialReportActions?: boolean; - - /** Are we loading more report actions? */ - isLoadingOlderReportActions?: boolean; - - /** Was there an error when loading older report actions? */ - hasLoadingOlderReportActionsError?: boolean; - - /** Are we loading newer report actions? */ - isLoadingNewerReportActions?: boolean; - - /** Was there an error when loading newer report actions? */ - hasLoadingNewerReportActionsError?: boolean; - /** Callback executed on list layout */ onLayout: (event: LayoutChangeEvent) => void; @@ -157,11 +142,6 @@ function ReportActionsList({ report, transactionThreadReport, parentReportAction, - isLoadingInitialReportActions = false, - isLoadingOlderReportActions = false, - hasLoadingOlderReportActionsError = false, - isLoadingNewerReportActions = false, - hasLoadingNewerReportActionsError = false, sortedReportActions, sortedVisibleReportActions, onScroll, @@ -206,7 +186,6 @@ function ReportActionsList({ const scrollingVerticalOffset = useRef(0); const readActionSkipped = useRef(false); const hasHeaderRendered = useRef(false); - const hasFooterRendered = useRef(false); const linkedReportActionID = route?.params?.reportActionID; const lastAction = sortedVisibleReportActions.at(0); @@ -674,40 +653,6 @@ function ReportActionsList({ // eslint-disable-next-line react-compiler/react-compiler const canShowHeader = isOffline || hasHeaderRendered.current; - const contentContainerStyle: StyleProp = useMemo( - () => [styles.chatContentScrollView, isLoadingNewerReportActions && canShowHeader ? styles.chatContentScrollViewWithHeaderLoader : {}], - [isLoadingNewerReportActions, styles.chatContentScrollView, styles.chatContentScrollViewWithHeaderLoader, canShowHeader], - ); - - const lastReportAction: OnyxTypes.ReportAction | undefined = useMemo(() => sortedReportActions.at(-1) ?? undefined, [sortedReportActions]); - - const retryLoadOlderChatsError = useCallback(() => { - loadOlderChats(true); - }, [loadOlderChats]); - - // eslint-disable-next-line react-compiler/react-compiler - const listFooterComponent = useMemo(() => { - // Skip this hook on the first render (when online), as we are not sure if more actions are going to be loaded, - // Therefore showing the skeleton on footer might be misleading. - // When offline, there should be no second render, so we should show the skeleton if the corresponding loading prop is present. - // In case of an error we want to display the footer no matter what. - if (!isOffline && !hasFooterRendered.current && !hasLoadingOlderReportActionsError) { - hasFooterRendered.current = true; - return null; - } - - return ( - - ); - }, [isLoadingInitialReportActions, isLoadingOlderReportActions, lastReportAction?.actionName, isOffline, hasLoadingOlderReportActionsError, retryLoadOlderChatsError]); - const onLayoutInner = useCallback( (event: LayoutChangeEvent) => { onLayout(event); @@ -728,7 +673,7 @@ function ReportActionsList({ const listHeaderComponent = useMemo(() => { // In case of an error we want to display the header no matter what. - if (!canShowHeader && !hasLoadingNewerReportActionsError) { + if (!canShowHeader) { // eslint-disable-next-line react-compiler/react-compiler hasHeaderRendered.current = true; return null; @@ -737,12 +682,10 @@ function ReportActionsList({ return ( ); - }, [isLoadingNewerReportActions, canShowHeader, hasLoadingNewerReportActionsError, retryLoadNewerChatsError]); + }, [canShowHeader, retryLoadNewerChatsError]); const onStartReached = useCallback(() => { if (!isSearchTopmostFullScreenRoute()) { @@ -780,14 +723,13 @@ function ReportActionsList({ style={styles.overscrollBehaviorContain} data={sortedVisibleReportActions} renderItem={renderItem} - contentContainerStyle={contentContainerStyle} + contentContainerStyle={styles.chatContentScrollView} keyExtractor={keyExtractor} initialNumToRender={initialNumToRender} onEndReached={onEndReached} onEndReachedThreshold={0.75} onStartReached={onStartReached} onStartReachedThreshold={0.75} - ListFooterComponent={listFooterComponent} ListHeaderComponent={listHeaderComponent} keyboardShouldPersistTaps="handled" onLayout={onLayoutInner} diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index e5e715c16d86..88b4cae332ed 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -1,14 +1,19 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; -import lodashIsEqual from 'lodash/isEqual'; import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; +import EmptyStateComponent from '@components/EmptyStateComponent'; +import * as Illustrations from '@components/Icon/Illustrations'; +import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; +import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; +import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import {getNewerActions, getOlderActions, openReport, updateLoadingInitialReportAction} from '@libs/actions/Report'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getNewerActions, getOlderActions, updateLoadingInitialReportAction} from '@libs/actions/Report'; import Timing from '@libs/actions/Timing'; import DateUtils from '@libs/DateUtils'; import getIsReportFullyVisible from '@libs/getIsReportFullyVisible'; @@ -27,9 +32,7 @@ import { isMoneyRequestAction, shouldReportActionBeVisible, } from '@libs/ReportActionsUtils'; -import {buildOptimisticCreatedReportAction, buildOptimisticIOUReportAction, canUserPerformWriteAction, isMoneyRequestReport, isUserCreatedPolicyRoom} from '@libs/ReportUtils'; -import {didUserLogInDuringSession} from '@libs/SessionUtils'; -import shouldFetchReport from '@libs/shouldFetchReport'; +import {buildOptimisticCreatedReportAction, buildOptimisticIOUReportAction, canUserPerformWriteAction, isMoneyRequestReport} from '@libs/ReportUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -53,18 +56,6 @@ type ReportActionsViewProps = { /** The report metadata loading states */ isLoadingInitialReportActions?: boolean; - /** The report actions are loading more data */ - isLoadingOlderReportActions?: boolean; - - /** There an error when loading older report actions */ - hasLoadingOlderReportActionsError?: boolean; - - /** The report actions are loading newer data */ - isLoadingNewerReportActions?: boolean; - - /** There an error when loading newer report actions */ - hasLoadingNewerReportActionsError?: boolean; - /** The reportID of the transaction thread report associated with this current report, if any */ // eslint-disable-next-line react/no-unused-prop-types transactionThreadReportID?: string | null; @@ -81,25 +72,21 @@ let listOldID = Math.round(Math.random() * 100); function ReportActionsView({ report, parentReportAction, - reportActions: allReportActions = [], - isLoadingInitialReportActions = false, - isLoadingOlderReportActions = false, - hasLoadingOlderReportActionsError = false, - isLoadingNewerReportActions = false, - hasLoadingNewerReportActionsError = false, + reportActions: allReportActions, + isLoadingInitialReportActions, transactionThreadReportID, hasNewerActions, hasOlderActions, }: ReportActionsViewProps) { useCopySelectionHelper(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); const reactionListRef = useContext(ReactionListContext); const route = useRoute>(); - const [session] = useOnyx(ONYXKEYS.SESSION); const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`, { selector: (reportActions: OnyxEntry) => getSortedReportActionsForDisplay(reportActions, canUserPerformWriteAction(report), true), }); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`); - const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`); const prevTransactionThreadReport = usePrevious(transactionThreadReport); const reportActionID = route?.params?.reportActionID; const prevReportActionID = usePrevious(reportActionID); @@ -108,22 +95,13 @@ function ReportActionsView({ const didLoadNewerChats = useRef(false); const {isOffline} = useNetwork(); - const network = useNetwork(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const contentListHeight = useRef(0); const isFocused = useIsFocused(); - const prevAuthTokenType = usePrevious(session?.authTokenType); const [isNavigatingToLinkedMessage, setNavigatingToLinkedMessage] = useState(!!reportActionID); const prevShouldUseNarrowLayoutRef = useRef(shouldUseNarrowLayout); const reportID = report.reportID; const isReportFullyVisible = useMemo((): boolean => getIsReportFullyVisible(isFocused), [isFocused]); - const openReportIfNecessary = () => { - if (!shouldFetchReport(report, reportMetadata?.isOptimisticReport)) { - return; - } - - openReport(reportID, reportActionID); - }; useEffect(() => { // When we linked to message - we do not need to wait for initial actions - they already exists @@ -153,7 +131,7 @@ function ReportActionsView({ // and we also generate an expense action if the number of expenses in allReportActions is less than the total number of expenses // to display at least one expense action to match the total data. const reportActionsToDisplay = useMemo(() => { - if (!isMoneyRequestReport(report) || !allReportActions.length) { + if (!isMoneyRequestReport(report) || !allReportActions?.length) { return allReportActions; } @@ -210,7 +188,7 @@ function ReportActionsView({ // Get a sorted array of reportActions for both the current report and the transaction thread report associated with this report (if there is one) // so that we display transaction-level and report-level report actions in order in the one-transaction view const reportActions = useMemo( - () => getCombinedReportActions(reportActionsToDisplay, transactionThreadReportID ?? null, transactionThreadReportActions ?? []), + () => (reportActionsToDisplay ? getCombinedReportActions(reportActionsToDisplay, transactionThreadReportID ?? null, transactionThreadReportActions ?? []) : []), [reportActionsToDisplay, transactionThreadReportActions, transactionThreadReportID], ); @@ -218,7 +196,7 @@ function ReportActionsView({ () => isEmptyObject(transactionThreadReportActions) ? undefined - : (allReportActions.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID) as OnyxEntry), + : (allReportActions?.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID) as OnyxEntry), [allReportActions, transactionThreadReportActions, transactionThreadReport?.parentReportActionID], ); @@ -234,10 +212,10 @@ function ReportActionsView({ ); const reportActionIDMap = useMemo(() => { - const reportActionIDs = allReportActions.map((action) => action.reportActionID); + const reportActionIDs = allReportActions?.map((action) => action.reportActionID); return reportActions.map((action) => ({ reportActionID: action.reportActionID, - reportID: reportActionIDs.includes(action.reportActionID) ? reportID : transactionThreadReport?.reportID, + reportID: reportActionIDs?.includes(action.reportActionID) ? reportID : transactionThreadReport?.reportID, })); }, [allReportActions, reportID, transactionThreadReport, reportActions]); @@ -250,22 +228,6 @@ function ReportActionsView({ const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]); useEffect(() => { - const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS && !session?.authTokenType; - if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(report)) { - openReportIfNecessary(); - } - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [session, report]); - - useEffect(() => { - const prevShouldUseNarrowLayout = prevShouldUseNarrowLayoutRef.current; - // If the view is expanded from mobile to desktop layout - // we update the new marker position, mark the report as read, and fetch new report actions - const didScreenSizeIncrease = prevShouldUseNarrowLayout && !shouldUseNarrowLayout; - const didReportBecomeVisible = isReportFullyVisible && didScreenSizeIncrease; - if (didReportBecomeVisible) { - openReportIfNecessary(); - } // update ref with current state prevShouldUseNarrowLayoutRef.current = shouldUseNarrowLayout; // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps @@ -282,14 +244,7 @@ function ReportActionsView({ const loadOlderChats = useCallback( (force = false) => { // Only fetch more if we are neither already fetching (so that we don't initiate duplicate requests) nor offline. - if ( - !force && - (!!network.isOffline || - isLoadingOlderReportActions || - // If there was an error only try again once on initial mount. - (didLoadOlderChats.current && hasLoadingOlderReportActionsError) || - isLoadingInitialReportActions) - ) { + if (!force && isOffline) { return; } @@ -313,17 +268,7 @@ function ReportActionsView({ getOlderActions(reportID, oldestReportAction.reportActionID); } }, - [ - network.isOffline, - isLoadingOlderReportActions, - isLoadingInitialReportActions, - oldestReportAction, - reportID, - reportActionIDMap, - transactionThreadReport, - hasLoadingOlderReportActionsError, - hasOlderActions, - ], + [isOffline, oldestReportAction, reportID, reportActionIDMap, transactionThreadReport, hasOlderActions], ); const loadNewerChats = useCallback( @@ -333,13 +278,11 @@ function ReportActionsView({ (!reportActionID || !isFocused || !newestReportAction || - isLoadingInitialReportActions || - isLoadingNewerReportActions || !hasNewerActions || isOffline || // If there was an error only try again once on initial mount. We should also still load // more in case we have cached messages. - (didLoadNewerChats.current && hasLoadingNewerReportActionsError) || + didLoadNewerChats.current || newestReportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) ) { return; @@ -360,19 +303,7 @@ function ReportActionsView({ getNewerActions(reportID, newestReportAction.reportActionID); } }, - [ - reportActionID, - isFocused, - newestReportAction, - isLoadingInitialReportActions, - isLoadingNewerReportActions, - hasNewerActions, - isOffline, - hasLoadingNewerReportActionsError, - transactionThreadReport, - reportActionIDMap, - reportID, - ], + [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID], ); /** @@ -420,9 +351,21 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - // Comments have not loaded at all yet do nothing - if (!reportActions.length) { - return null; + if (isLoadingInitialReportActions && visibleReportActions.length === 0 && !isOffline) { + return ; + } + + if (visibleReportActions.length === 0) { + return ( + + ); } // AutoScroll is disabled when we do linking to a specific reportAction @@ -440,11 +383,6 @@ function ReportActionsView({ mostRecentIOUReportActionID={mostRecentIOUReportActionID} loadOlderChats={loadOlderChats} loadNewerChats={loadNewerChats} - isLoadingInitialReportActions={isLoadingInitialReportActions} - isLoadingOlderReportActions={isLoadingOlderReportActions} - hasLoadingOlderReportActionsError={hasLoadingOlderReportActionsError} - isLoadingNewerReportActions={isLoadingNewerReportActions} - hasLoadingNewerReportActionsError={hasLoadingNewerReportActionsError} listID={listID} onContentSizeChange={onContentSizeChange} shouldEnableAutoScrollToTopThreshold={shouldEnableAutoScroll} @@ -457,46 +395,4 @@ function ReportActionsView({ ReportActionsView.displayName = 'ReportActionsView'; -function arePropsEqual(oldProps: ReportActionsViewProps, newProps: ReportActionsViewProps): boolean { - if (!lodashIsEqual(oldProps.reportActions, newProps.reportActions)) { - return false; - } - - if (!lodashIsEqual(oldProps.parentReportAction, newProps.parentReportAction)) { - return false; - } - - if (oldProps.isLoadingInitialReportActions !== newProps.isLoadingInitialReportActions) { - return false; - } - - if (oldProps.isLoadingOlderReportActions !== newProps.isLoadingOlderReportActions) { - return false; - } - - if (oldProps.isLoadingNewerReportActions !== newProps.isLoadingNewerReportActions) { - return false; - } - - if (oldProps.hasLoadingOlderReportActionsError !== newProps.hasLoadingOlderReportActionsError) { - return false; - } - - if (oldProps.hasLoadingNewerReportActionsError !== newProps.hasLoadingNewerReportActionsError) { - return false; - } - - if (oldProps.hasNewerActions !== newProps.hasNewerActions) { - return false; - } - - if (oldProps.hasOlderActions !== newProps.hasOlderActions) { - return false; - } - - return lodashIsEqual(oldProps.report, newProps.report); -} - -const MemoizedReportActionsView = React.memo(ReportActionsView, arePropsEqual); - -export default Performance.withRenderTrace({id: ' rendering'})(MemoizedReportActionsView); +export default Performance.withRenderTrace({id: ' rendering'})(ReportActionsView); diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index 03f32f8ca846..73df63153b1d 100644 --- a/tests/ui/PaginationTest.tsx +++ b/tests/ui/PaginationTest.tsx @@ -4,10 +4,10 @@ import {act, fireEvent, render, screen, within} from '@testing-library/react-nat import {addSeconds, format, subMinutes} from 'date-fns'; import React from 'react'; import Onyx from 'react-native-onyx'; -import * as Localize from '@libs/Localize'; -import * as SequentialQueue from '@libs/Network/SequentialQueue'; -import * as AppActions from '@userActions/App'; -import * as User from '@userActions/User'; +import {translateLocal} from '@libs/Localize'; +import {waitForIdle} from '@libs/Network/SequentialQueue'; +import {setSidebarLoaded} from '@userActions/App'; +import {subscribeToUserEvents} from '@userActions/User'; import App from '@src/App'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -52,7 +52,7 @@ function getReportScreen(reportID = REPORT_ID) { } function scrollToOffset(offset: number) { - const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages'); + const hintText = translateLocal('sidebarScreen.listOfChatMessages'); fireEvent.scroll(within(getReportScreen()).getByLabelText(hintText), { nativeEvent: { contentOffset: { @@ -82,9 +82,9 @@ function triggerListLayout(reportID?: string) { function getReportActions(reportID?: string) { const report = getReportScreen(reportID); return [ - ...within(report).queryAllByLabelText(Localize.translateLocal('accessibilityHints.chatMessage')), + ...within(report).queryAllByLabelText(translateLocal('accessibilityHints.chatMessage')), // Created action has a different accessibility label. - ...within(report).queryAllByLabelText(Localize.translateLocal('accessibilityHints.chatWelcomeMessage')), + ...within(report).queryAllByLabelText(translateLocal('accessibilityHints.chatWelcomeMessage')), ]; } @@ -192,7 +192,7 @@ async function signInAndGetApp(): Promise { // Render the App and sign in as a test user. render(); await waitForBatchedUpdatesWithAct(); - const hintText = Localize.translateLocal('loginForm.loginForm'); + const hintText = translateLocal('loginForm.loginForm'); const loginForm = await screen.findAllByLabelText(hintText); expect(loginForm).toHaveLength(1); @@ -202,7 +202,7 @@ async function signInAndGetApp(): Promise { await waitForBatchedUpdatesWithAct(); - User.subscribeToUserEvents(); + subscribeToUserEvents(); await waitForBatchedUpdates(); @@ -253,7 +253,7 @@ async function signInAndGetApp(): Promise { }); // We manually setting the sidebar as loaded since the onLayout event does not fire in tests - AppActions.setSidebarLoaded(); + setSidebarLoaded(); }); await waitForBatchedUpdatesWithAct(); @@ -261,7 +261,7 @@ async function signInAndGetApp(): Promise { describe('Pagination', () => { afterEach(async () => { - await SequentialQueue.waitForIdle(); + await waitForIdle(); await act(async () => { await Onyx.clear(); @@ -346,7 +346,7 @@ describe('Pagination', () => { expect(getReportActions()).toHaveLength(10); // There is 1 extra call here because of the comment linking report. - TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 2); + TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3); TestHelper.expectAPICommandToHaveBeenCalledWith('OpenReport', 1, {reportID: REPORT_ID, reportActionID: '5'}); TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0); TestHelper.expectAPICommandToHaveBeenCalledWith('GetNewerActions', 0, {reportID: REPORT_ID, reportActionID: '5'}); @@ -357,13 +357,12 @@ describe('Pagination', () => { scrollToOffset(0); await waitForBatchedUpdatesWithAct(); - TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 2); + TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3); TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0); - TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 2); - TestHelper.expectAPICommandToHaveBeenCalledWith('GetNewerActions', 1, {reportID: REPORT_ID, reportActionID: '10'}); + TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1); - // We now have 15 messages. 5 from the initial OpenReport and 10 from the 2 GetNewerActions calls. - expect(getReportActions()).toHaveLength(15); + // We now have 10 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call. + expect(getReportActions()).toHaveLength(10); // Simulate the backend returning no new messages to simulate reaching the start of the chat. mockGetNewerActions(0); @@ -373,12 +372,11 @@ describe('Pagination', () => { scrollToOffset(0); await waitForBatchedUpdatesWithAct(); - TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 2); + TestHelper.expectAPICommandToHaveBeenCalled('OpenReport', 3); TestHelper.expectAPICommandToHaveBeenCalled('GetOlderActions', 0); - TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 3); - TestHelper.expectAPICommandToHaveBeenCalledWith('GetNewerActions', 2, {reportID: REPORT_ID, reportActionID: '15'}); + TestHelper.expectAPICommandToHaveBeenCalled('GetNewerActions', 1); - // We still have 15 messages. 5 from the initial OpenReport and 10 from the 2 GetNewerActions calls. - expect(getReportActions()).toHaveLength(15); + // We still have 15 messages. 5 from the initial OpenReport and 5 from the GetNewerActions call. + expect(getReportActions()).toHaveLength(10); }); });