Skip to content

Commit

Permalink
add comments, move usePaginatedReportActionList to the utils
Browse files Browse the repository at this point in the history
  • Loading branch information
perunt committed Mar 19, 2024
1 parent ad44807 commit c108d9d
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 101 deletions.
103 changes: 100 additions & 3 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import type {RouteProp} from '@react-navigation/native';
import fastMerge from 'expensify-common/lib/fastMerge';
import _ from 'lodash';
import lodashFindLast from 'lodash/findLast';
import {useCallback, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import getInitialPaginationSize from '@pages/home/report/getInitialPaginationSize';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import type {
ActionName,
ChangeLog,
Expand All @@ -26,6 +31,7 @@ import isReportMessageAttachment from './isReportMessageAttachment';
import * as Localize from './Localize';
import Log from './Log';
import type {MessageElementBase, MessageTextElement} from './MessageElement';
import type {CentralPaneNavigatorParamList} from './Navigation/types';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import type {OptimisticIOUReportAction} from './ReportUtils';

Expand Down Expand Up @@ -575,9 +581,7 @@ function getSortedReportActionsForDisplay(reportActions: ReportActions | null |
}

if (shouldIncludeInvisibleActions) {
filteredReportActions = Object.values(reportActions).filter(
(action): action is ReportAction => !(action?.originalMessage as OriginalMessageActionableMentionWhisper['originalMessage'])?.resolution,
);
filteredReportActions = Object.values(reportActions);
} else {
filteredReportActions = Object.entries(reportActions)
.filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key))
Expand Down Expand Up @@ -976,6 +980,98 @@ function getReportActionMessageText(reportAction: OnyxEntry<ReportAction> | Empt
return reportAction?.message?.reduce((acc, curr) => `${acc}${curr.text}`, '') ?? '';
}

let listIDCount = Math.round(Math.random() * 100);
/**
* usePaginatedReportActionList manages the logic for handling a list of messages with pagination and dynamic loading.
* It determines the part of the message array to display ('visibleReportActions') based on the current linked message,
* and manages pagination through 'handleReportActionPagination' function.
*
* linkedID - ID of the linked message used for initial focus.
* allReportActions - Array of messages.
* fetchNewerReportActions - Function to fetch more messages.
* route - Current route, used to reset states on route change.
* isLoading - Loading state indicator.
* triggerListID - Used to trigger a listID change.
* returns {object} An object containing the sliced message array, the pagination function,
* index of the linked message, and a unique list ID.
*/
const usePaginatedReportActionList = (
linkedID: string,
localAllReportActions: OnyxTypes.ReportAction[],
fetchNewerReportActions: (newestReportAction: OnyxTypes.ReportAction) => void,
route: RouteProp<CentralPaneNavigatorParamList, typeof SCREENS.REPORT>,
isLoading: boolean,
triggerListID: boolean,
) => {
// triggerListID is used when navigating to a chat with messages loaded from LHN. Typically, these include thread actions, task actions, etc. Since these messages aren't the latest, we don't maintain their position and instead trigger a recalculation of their positioning in the list.
// we don't set currentReportActionID on initial render as linkedID as it should trigger visibleReportActions after linked message was positioned
const [currentReportActionID, setCurrentReportActionID] = useState('');
const isFirstLinkedActionRender = useRef(true);

useLayoutEffect(() => {
setCurrentReportActionID('');
}, [route]);

const listID = useMemo(() => {
isFirstLinkedActionRender.current = true;
listIDCount += 1;
return listIDCount;
// This needs to be triggered with each navigation to a comment. It happens when the route is changed. triggerListID is needed to trigger a listID change after initial mounting.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [route, triggerListID]);

const index = useMemo(() => {
if (!linkedID || isLoading) {
return -1;
}

return localAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(isFirstLinkedActionRender.current ? linkedID : currentReportActionID));
}, [localAllReportActions, currentReportActionID, linkedID, isLoading]);

const visibleReportActions = useMemo(() => {
if (!linkedID) {
return localAllReportActions;
}
if (isLoading || index === -1) {
return [];
}

if (isFirstLinkedActionRender.current) {
return localAllReportActions.slice(index, localAllReportActions.length);
}
const paginationSize = getInitialPaginationSize();
const newStartIndex = index >= paginationSize ? index - paginationSize : 0;
return newStartIndex ? localAllReportActions.slice(newStartIndex, localAllReportActions.length) : localAllReportActions;
// currentReportActionID is needed to trigger batching once the report action has been positioned
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [linkedID, localAllReportActions, index, isLoading, currentReportActionID]);

const hasMoreCached = visibleReportActions.length < localAllReportActions?.length;
const newestReportAction = visibleReportActions?.[0];

const handleReportActionPagination = useCallback(
({firstReportActionID}: {firstReportActionID: string}) => {
// This function is a placeholder as the actual pagination is handled by visibleReportActions
if (!hasMoreCached) {
isFirstLinkedActionRender.current = false;
fetchNewerReportActions(newestReportAction);
}
if (isFirstLinkedActionRender.current) {
isFirstLinkedActionRender.current = false;
}
setCurrentReportActionID(firstReportActionID);
},
[fetchNewerReportActions, hasMoreCached, newestReportAction],
);

return {
visibleReportActions,
loadMoreReportActionsHandler: handleReportActionPagination,
linkedIdIndex: index,
listID,
};
};

export {
extractLinksFromMessageHtml,
getAllReportActions,
Expand Down Expand Up @@ -1033,6 +1129,7 @@ export {
isCurrentActionUnread,
isActionableJoinRequest,
isActionableJoinRequestPending,
usePaginatedReportActionList,
};

export type {LastVisibleMessage};
2 changes: 1 addition & 1 deletion src/pages/home/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ function ReportScreen({
return;
}

// It possible that we may not have the report object yet in Onyx yet e.g. we navigated to a URL for an accessible report that
// It is possible that we may not have the report object yet in Onyx yet e.g. we navigated to a URL for an accessible report that
// is not stored locally yet. If report.reportID exists, then the report has been stored locally and nothing more needs to be done.
// If it doesn't exist, then we fetch the report from the API.
if (report.reportID && report.reportID === reportIDFromRoute && !reportMetadata?.isLoadingInitialReportActions) {
Expand Down
98 changes: 2 additions & 96 deletions src/pages/home/report/ReportActionsView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {RouteProp} from '@react-navigation/native';
import {useIsFocused, useRoute} from '@react-navigation/native';
import lodashIsEqual from 'lodash/isEqual';
import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager} from 'react-native';
import type {LayoutChangeEvent} from 'react-native';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -25,7 +25,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import getInitialPaginationSize from './getInitialPaginationSize';
import PopoverReactionList from './ReactionList/PopoverReactionList';
import ReportActionsList from './ReportActionsList';

Expand Down Expand Up @@ -60,98 +59,6 @@ type ReportActionsViewProps = ReportActionsViewOnyxProps & {
const DIFF_BETWEEN_SCREEN_HEIGHT_AND_LIST = 120;
const SPACER = 16;

let listIDCount = Math.round(Math.random() * 100);

/**
* usePaginatedReportActionList manages the logic for handling a list of messages with pagination and dynamic loading.
* It determines the part of the message array to display ('visibleReportActions') based on the current linked message,
* and manages pagination through 'handleReportActionPagination' function.
*
* linkedID - ID of the linked message used for initial focus.
* allReportActions - Array of messages.
* fetchNewerReportActions - Function to fetch more messages.
* route - Current route, used to reset states on route change.
* isLoading - Loading state indicator.
* triggerListID - Used to trigger a listID change.
* returns {object} An object containing the sliced message array, the pagination function,
* index of the linked message, and a unique list ID.
*/
const usePaginatedReportActionList = (
linkedID: string,
allReportActions: OnyxTypes.ReportAction[],
fetchNewerReportActions: (newestReportAction: OnyxTypes.ReportAction) => void,
route: RouteProp<CentralPaneNavigatorParamList, typeof SCREENS.REPORT>,
isLoading: boolean,
triggerListID: boolean,
) => {
// triggerListID is used when navigating to a chat with messages loaded from LHN. Typically, these include thread actions, task actions, etc. Since these messages aren't the latest, we don't maintain their position and instead trigger a recalculation of their positioning in the list.
// we don't set currentReportActionID on initial render as linkedID as it should trigger visibleReportActions after linked message was positioned
const [currentReportActionID, setCurrentReportActionID] = useState('');
const isFirstLinkedActionRender = useRef(true);

useLayoutEffect(() => {
setCurrentReportActionID('');
}, [route]);

const listID = useMemo(() => {
isFirstLinkedActionRender.current = true;
listIDCount += 1;
return listIDCount;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [route, triggerListID]);

const index = useMemo(() => {
if (!linkedID || isLoading) {
return -1;
}

return allReportActions.findIndex((obj) => String(obj.reportActionID) === String(isFirstLinkedActionRender.current ? linkedID : currentReportActionID));
}, [allReportActions, currentReportActionID, linkedID, isLoading]);

const visibleReportActions = useMemo(() => {
if (!linkedID) {
return allReportActions;
}
if (isLoading || index === -1) {
return [];
}

if (isFirstLinkedActionRender.current) {
return allReportActions.slice(index, allReportActions.length);
}
const paginationSize = getInitialPaginationSize();
const newStartIndex = index >= paginationSize ? index - paginationSize : 0;
return newStartIndex ? allReportActions.slice(newStartIndex, allReportActions.length) : allReportActions;
// currentReportActionID is needed to trigger batching once the report action has been positioned
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [linkedID, allReportActions, index, isLoading, currentReportActionID]);

const hasMoreCached = visibleReportActions.length < allReportActions.length;
const newestReportAction = visibleReportActions?.[0];

const handleReportActionPagination = useCallback(
({firstReportActionID}: {firstReportActionID: string}) => {
// This function is a placeholder as the actual pagination is handled by visibleReportActions
if (!hasMoreCached) {
isFirstLinkedActionRender.current = false;
fetchNewerReportActions(newestReportAction);
}
if (isFirstLinkedActionRender.current) {
isFirstLinkedActionRender.current = false;
}
setCurrentReportActionID(firstReportActionID);
},
[fetchNewerReportActions, hasMoreCached, newestReportAction],
);

return {
visibleReportActions,
loadMoreReportActionsHandler: handleReportActionPagination,
linkedIdIndex: index,
listID,
};
};

function ReportActionsView({
report,
session,
Expand Down Expand Up @@ -202,7 +109,7 @@ function ReportActionsView({
loadMoreReportActionsHandler,
linkedIdIndex,
listID,
} = usePaginatedReportActionList(reportActionID, allReportActions, fetchNewerAction, route, isLoading, isLoadingInitialReportActions);
} = ReportActionsUtils.usePaginatedReportActionList(reportActionID, allReportActions, fetchNewerAction, route, isLoading, isLoadingInitialReportActions);
const mostRecentIOUReportActionID = useMemo(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActions), [reportActions]);
const hasCachedActions = useInitialValue(() => reportActions.length > 0);
const hasNewestReportAction = reportActions[0]?.created === report.lastVisibleActionCreated;
Expand Down Expand Up @@ -453,7 +360,6 @@ function ReportActionsView({
mostRecentIOUReportActionID={mostRecentIOUReportActionID}
loadOlderChats={loadOlderChats}
loadNewerChats={loadNewerChats}
// isLinkingLoader={!!reportActionID && isLoadingInitialReportActions}
isLoadingInitialReportActions={isLoadingInitialReportActions}
isLoadingOlderReportActions={isLoadingOlderReportActions}
isLoadingNewerReportActions={isLoadingNewerReportActions}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const DEFAULT_NUM_TO_RENDER = 50;

function getInitialNumToRender(numToRender: number): number {
// For web and desktop environments, it's crucial to set this value equal to or higher than the maxToRenderPerBatch setting. If it's set lower, the 'onStartReached' event will be triggered excessively, every time an additional item enters the virtualized list.
return Math.max(numToRender, 50);
return Math.max(numToRender, DEFAULT_NUM_TO_RENDER);
}
export default getInitialNumToRender;

0 comments on commit c108d9d

Please sign in to comment.