From c767c1c76c811b704850f606372f7f191491be82 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 12 Feb 2025 19:34:17 +0100 Subject: [PATCH 1/8] fix: infinite loaders in report section --- .../ReportActionItemImages.tsx | 10 +- src/components/TextBlock.tsx | 1 + src/pages/home/ReportScreen.tsx | 186 ++---------------- src/pages/home/report/ReportActionsList.tsx | 66 +------ src/pages/home/report/ReportActionsView.tsx | 130 +++--------- 5 files changed, 54 insertions(+), 339 deletions(-) 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 ( - - + + ( {word} diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 68046fedc7d5..7953eaa4e3e2 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -13,7 +13,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; @@ -34,29 +33,24 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; -import {getDisplayNameOrDefault, isPersonalDetailsEmpty} from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; import { getCombinedReportActions, getOneTransactionThreadReportID, - isActionOfType, isCreatedAction, - isDeletedAction, isDeletedParentAction, isMoneyRequestAction, isWhisperAction, shouldReportActionBeVisible, } from '@libs/ReportActionsUtils'; import { - canAccessReport, canEditReportAction, canUserPerformWriteAction, findLastAccessedReport, getParticipantsAccountIDsForDisplay, - getReportIDFromLink, getReportOfflinePendingActionAndErrors, isChatThread, isConciergeChatReport, - isExpenseReport, isGroupChat, isHiddenForCurrentUser, isInvoiceReport, @@ -69,7 +63,6 @@ import { isTrackExpenseReport, isValidReportIDFromPath, } from '@libs/ReportUtils'; -import shouldFetchReport from '@libs/shouldFetchReport'; import {isNumeric} from '@libs/ValidationUtils'; import type {AuthScreensParamList} from '@navigation/types'; import {setShouldShowComposeInput} from '@userActions/Composer'; @@ -88,9 +81,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import HeaderView from './HeaderView'; -import ReportActionsListItemRenderer from './report/ReportActionsListItemRenderer'; import ReportActionsView from './report/ReportActionsView'; import ReportFooter from './report/ReportFooter'; import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext'; @@ -153,23 +144,17 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [accountManagerReportID] = useOnyx(ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID); const [accountManagerReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(accountManagerReportID)}`); const [userLeavingStatus] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`, {initialValue: false}); - const [reportOnyx, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true}); + const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true}); const [reportNameValuePairsOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportIDFromRoute}`, {allowStaleData: true}); const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {initialValue: defaultReportMetadata}); - const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED, {initialValue: false}); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true, initialValue: {}}); - const [betas] = useOnyx(ONYXKEYS.BETAS); const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportOnyx?.parentReportID)}`, { canEvict: false, selector: (parentReportActions) => 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(() => { @@ -193,7 +178,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(() => { @@ -290,7 +275,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; @@ -299,10 +283,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)); @@ -314,12 +294,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [showSoftInputOnFocus, setShowSoftInputOnFocus] = useState(false); useEffect(() => { - if (!report?.reportID || shouldHideReport) { + if (!report?.reportID) { wasReportAccessibleRef.current = false; return; } wasReportAccessibleRef.current = true; - }, [shouldHideReport, report]); + }, [report]); const onBackButtonPress = useCallback(() => { if (isInNarrowPaneModal) { @@ -378,10 +358,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], @@ -412,22 +388,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 && @@ -443,41 +407,16 @@ 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(() => { @@ -504,38 +443,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); }, []); @@ -575,25 +482,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(() => { @@ -663,14 +557,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, @@ -722,18 +613,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(); @@ -776,18 +655,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); @@ -847,48 +714,19 @@ function ReportScreen({route, navigation}: ReportScreenProps) { style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]} testID="report-actions-view-wrapper" > - {!shouldShowSkeleton && !!report && ( + {!!report && ( )} - {/* 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 ? ( 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 (!isSearchTopmostCentralPane()) { @@ -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 99327cbef2d8..66893412b0b3 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -4,11 +4,18 @@ import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} fr 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 LoadingBar from '@components/LoadingBar'; +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 +34,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'; @@ -56,15 +61,6 @@ type ReportActionsViewProps = { /** 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; @@ -84,22 +80,19 @@ function ReportActionsView({ reportActions: allReportActions = [], isLoadingInitialReportActions = false, isLoadingOlderReportActions = false, - hasLoadingOlderReportActionsError = false, - isLoadingNewerReportActions = false, - hasLoadingNewerReportActionsError = false, 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 +101,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 @@ -250,22 +234,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 +250,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 || isLoadingInitialReportActions)) { return; } @@ -313,17 +274,7 @@ function ReportActionsView({ getOlderActions(reportID, oldestReportAction.reportActionID); } }, - [ - network.isOffline, - isLoadingOlderReportActions, - isLoadingInitialReportActions, - oldestReportAction, - reportID, - reportActionIDMap, - transactionThreadReport, - hasLoadingOlderReportActionsError, - hasOlderActions, - ], + [isOffline, isLoadingInitialReportActions, oldestReportAction, reportID, reportActionIDMap, transactionThreadReport, hasOlderActions], ); const loadNewerChats = useCallback( @@ -333,13 +284,12 @@ function ReportActionsView({ (!reportActionID || !isFocused || !newestReportAction || - isLoadingInitialReportActions || - isLoadingNewerReportActions || + !!isLoadingInitialReportActions || !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 +310,7 @@ function ReportActionsView({ getNewerActions(reportID, newestReportAction.reportActionID); } }, - [ - reportActionID, - isFocused, - newestReportAction, - isLoadingInitialReportActions, - isLoadingNewerReportActions, - hasNewerActions, - isOffline, - hasLoadingNewerReportActionsError, - transactionThreadReport, - reportActionIDMap, - reportID, - ], + [reportActionID, isFocused, newestReportAction, isLoadingInitialReportActions, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID], ); /** @@ -420,15 +358,28 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - // Comments have not loaded at all yet do nothing - if (!reportActions.length) { - return null; + if ((!visibleReportActions.length && !isLoadingInitialReportActions && !isOffline) || (!visibleReportActions.length && isOffline)) { + return ( + + ); + } + + if (!visibleReportActions.length && isLoadingInitialReportActions) { + return ; } // AutoScroll is disabled when we do linking to a specific reportAction const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport); return ( <> + Date: Mon, 17 Feb 2025 18:59:38 +0100 Subject: [PATCH 2/8] fix infinite loaders: removed loading bar and redundant props --- src/pages/home/ReportScreen.tsx | 1 - src/pages/home/report/ReportActionsView.tsx | 14 ++------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 58639f6475af..415b2a002cad 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -727,7 +727,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { report={report} reportActions={reportActions} isLoadingInitialReportActions={reportMetadata?.isLoadingInitialReportActions} - isLoadingOlderReportActions={reportMetadata?.isLoadingOlderReportActions} hasNewerActions={hasNewerActions} hasOlderActions={hasOlderActions} parentReportAction={parentReportAction} diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index f902ba871aef..c7098d7e9cce 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -6,7 +6,6 @@ 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 LoadingBar from '@components/LoadingBar'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; @@ -58,9 +57,6 @@ type ReportActionsViewProps = { /** The report metadata loading states */ isLoadingInitialReportActions?: boolean; - /** The report actions are loading more data */ - isLoadingOlderReportActions?: 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; @@ -78,8 +74,7 @@ function ReportActionsView({ report, parentReportAction, reportActions: allReportActions = [], - isLoadingInitialReportActions = false, - isLoadingOlderReportActions = false, + isLoadingInitialReportActions, transactionThreadReportID, hasNewerActions, hasOlderActions, @@ -358,7 +353,7 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - if ((!visibleReportActions.length && !isLoadingInitialReportActions && !isOffline) || (!visibleReportActions.length && isOffline)) { + if ((visibleReportActions.length === 0 && !isLoadingInitialReportActions && !isOffline) || (visibleReportActions.length === 0 && isOffline)) { return ( - Date: Tue, 18 Feb 2025 09:27:08 +0100 Subject: [PATCH 3/8] fix infinite loaders: fix tests --- tests/ui/PaginationTest.tsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index 03f32f8ca846..237c8943a999 100644 --- a/tests/ui/PaginationTest.tsx +++ b/tests/ui/PaginationTest.tsx @@ -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); }); }); From e15d05d36cbbbd26482eb630ffcac45d4d600063 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Tue, 18 Feb 2025 09:48:21 +0100 Subject: [PATCH 4/8] fix infinite loaders: fix linter errors --- tests/ui/PaginationTest.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/ui/PaginationTest.tsx b/tests/ui/PaginationTest.tsx index 237c8943a999..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(); From 9ef0b6ddb72e799c6fc0e057c001aa3edbd6efab Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 24 Feb 2025 18:19:22 +0100 Subject: [PATCH 5/8] fix infinite loaders: initial loader on app opening & empty state vs skeleton loader distinction --- src/pages/home/ReportScreen.tsx | 15 ++++-- src/pages/home/report/ReportActionsView.tsx | 59 ++++++--------------- 2 files changed, 28 insertions(+), 46 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 415b2a002cad..7627e77610fd 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -13,6 +13,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MoneyReportHeader from '@components/MoneyReportHeader'; import MoneyRequestHeader from '@components/MoneyRequestHeader'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useAppFocusEvent from '@hooks/useAppFocusEvent'; @@ -147,12 +148,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [userLeavingStatus] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`, {initialValue: false}); const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true}); const [reportNameValuePairsOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportIDFromRoute}`, {allowStaleData: true}); - const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {initialValue: defaultReportMetadata}); + const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {initialValue: defaultReportMetadata}); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true, initialValue: {}}); const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportOnyx?.parentReportID)}`, { canEvict: false, selector: (parentReportActions) => getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID), }); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const deletedParentAction = isDeletedParentAction(parentReportAction); const prevDeletedParentAction = usePrevious(deletedParentAction); @@ -257,6 +259,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); @@ -399,7 +402,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundLinkedAction = (!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted && isNavigatingToDeletedAction) || - (!reportMetadata.isLoadingInitialReportActions && + (!reportMetadata?.isLoadingInitialReportActions && !!reportActionIDFromRoute && !!sortedAllReportActions && sortedAllReportActions?.length > 0 && @@ -415,6 +418,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return true; } + if (isLoadingApp !== false) { + 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 @@ -722,7 +729,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]} testID="report-actions-view-wrapper" > - {!!report && ( + {!!report && !isLoadingReportData ? ( + ) : ( + )} {isCurrentReportLoadedFromOnyx ? ( diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index c7098d7e9cce..1cdaccccdbe9 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -1,5 +1,4 @@ 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'; @@ -73,7 +72,7 @@ let listOldID = Math.round(Math.random() * 100); function ReportActionsView({ report, parentReportAction, - reportActions: allReportActions = [], + reportActions: allReportActions, isLoadingInitialReportActions, transactionThreadReportID, hasNewerActions, @@ -132,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; } @@ -189,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], ); @@ -197,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], ); @@ -213,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]); @@ -227,6 +226,7 @@ function ReportActionsView({ actionCreated && lastVisibleActionCreated ? actionCreated >= lastVisibleActionCreated : actionCreated === lastVisibleActionCreated; const hasNewestReportAction = isNewestAction(lastActionCreated, report.lastVisibleActionCreated) || isNewestAction(lastActionCreated, transactionThreadReport?.lastVisibleActionCreated); const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]); + const isLoading = isLoadingInitialReportActions !== false && !visibleReportActions.length; useEffect(() => { // update ref with current state @@ -245,7 +245,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 && (isOffline || isLoadingInitialReportActions)) { + if (!force && isOffline) { return; } @@ -269,7 +269,7 @@ function ReportActionsView({ getOlderActions(reportID, oldestReportAction.reportActionID); } }, - [isOffline, isLoadingInitialReportActions, oldestReportAction, reportID, reportActionIDMap, transactionThreadReport, hasOlderActions], + [isOffline, oldestReportAction, reportID, reportActionIDMap, transactionThreadReport, hasOlderActions], ); const loadNewerChats = useCallback( @@ -279,7 +279,6 @@ function ReportActionsView({ (!reportActionID || !isFocused || !newestReportAction || - !!isLoadingInitialReportActions || !hasNewerActions || isOffline || // If there was an error only try again once on initial mount. We should also still load @@ -305,7 +304,7 @@ function ReportActionsView({ getNewerActions(reportID, newestReportAction.reportActionID); } }, - [reportActionID, isFocused, newestReportAction, isLoadingInitialReportActions, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID], + [reportActionID, isFocused, newestReportAction, hasNewerActions, isOffline, transactionThreadReport, reportActionIDMap, reportID], ); /** @@ -353,7 +352,11 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - if ((visibleReportActions.length === 0 && !isLoadingInitialReportActions && !isOffline) || (visibleReportActions.length === 0 && isOffline)) { + if (isLoading && !isOffline) { + return ; + } + + if (visibleReportActions.length === 0) { return ( ; - } - // AutoScroll is disabled when we do linking to a specific reportAction const shouldEnableAutoScroll = (hasNewestReportAction && (!reportActionID || !isNavigatingToLinkedMessage)) || (transactionThreadReport && !prevTransactionThreadReport); return ( @@ -397,30 +396,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.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); From 230c60c52af47d91e15c5edb01d75bdad4060295 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Mon, 24 Feb 2025 18:32:16 +0100 Subject: [PATCH 6/8] fix infinite loaders: fix linter errors --- src/components/TextBlock.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/TextBlock.tsx b/src/components/TextBlock.tsx index bc542491460b..8b036f42f4cc 100644 --- a/src/components/TextBlock.tsx +++ b/src/components/TextBlock.tsx @@ -25,7 +25,6 @@ function TextBlock({color, textStyles, text}: TextBlockProps) { {words.map((word) => ( {word} From 47de15d1410cd631d3bdcb751d2b54f1fa63ef67 Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 26 Feb 2025 17:29:02 +0100 Subject: [PATCH 7/8] fix infinite loaders: fix blinking report view and redundant skeleton --- src/pages/home/ReportScreen.tsx | 4 ++-- src/pages/home/report/ReportActionsView.tsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 7627e77610fd..421dd71cffe4 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -148,7 +148,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [userLeavingStatus] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM}${reportIDFromRoute}`, {initialValue: false}); const [reportOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportIDFromRoute}`, {allowStaleData: true}); const [reportNameValuePairsOnyx] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportIDFromRoute}`, {allowStaleData: true}); - const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {initialValue: defaultReportMetadata}); + const [reportMetadata = defaultReportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${reportIDFromRoute}`, {initialValue: defaultReportMetadata}); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true, initialValue: {}}); const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(reportOnyx?.parentReportID)}`, { canEvict: false, @@ -729,7 +729,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]} testID="report-actions-view-wrapper" > - {!!report && !isLoadingReportData ? ( + {!!report && !isLoadingApp ? ( = lastVisibleActionCreated : actionCreated === lastVisibleActionCreated; const hasNewestReportAction = isNewestAction(lastActionCreated, report.lastVisibleActionCreated) || isNewestAction(lastActionCreated, transactionThreadReport?.lastVisibleActionCreated); const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]); - const isLoading = isLoadingInitialReportActions !== false && !visibleReportActions.length; useEffect(() => { // update ref with current state @@ -352,7 +351,7 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - if (isLoading && !isOffline) { + if (isLoadingInitialReportActions && !isOffline) { return ; } From 0a19572f4d88c38dbb015951b8a69ed25473136e Mon Sep 17 00:00:00 2001 From: "marta.sudol" Date: Wed, 26 Feb 2025 17:49:02 +0100 Subject: [PATCH 8/8] fix infinite loaders: fix redundant skeleton issue --- src/pages/home/ReportScreen.tsx | 1 - src/pages/home/report/ReportActionsView.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 421dd71cffe4..a9cafa8c14a1 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -154,7 +154,6 @@ function ReportScreen({route, navigation}: ReportScreenProps) { canEvict: false, selector: (parentReportActions) => getParentReportAction(parentReportActions, reportOnyx?.parentReportActionID), }); - const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const deletedParentAction = isDeletedParentAction(parentReportAction); const prevDeletedParentAction = usePrevious(deletedParentAction); diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 0bd61d18b7ac..88b4cae332ed 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -351,7 +351,7 @@ function ReportActionsView({ }; }, [isTheFirstReportActionIsLinked]); - if (isLoadingInitialReportActions && !isOffline) { + if (isLoadingInitialReportActions && visibleReportActions.length === 0 && !isOffline) { return ; }