diff --git a/src/CONST.ts b/src/CONST.ts
index a983a18e3d6a..6517ece4276d 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -4767,6 +4767,7 @@ const CONST = {
DISTANCE: 'distance',
},
+ SEARCH_RESULTS_PAGE_SIZE: 50,
SEARCH_BOTTOM_TAB_URL: '/Search_Bottom_Tab',
SEARCH_DATA_TYPES: {
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index a8e469da7d99..27e87017bfee 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -9,6 +9,7 @@ import * as SearchUtils from '@libs/SearchUtils';
import Navigation from '@navigation/Navigation';
import EmptySearchView from '@pages/Search/EmptySearchView';
import useCustomBackHandler from '@pages/Search/useCustomBackHandler';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
@@ -35,14 +36,15 @@ function Search({query, policyIDs}: SearchProps) {
return;
}
- SearchActions.search(hash, query, policyIDs);
+ SearchActions.search(hash, query, 0, policyIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash, isOffline]);
- const isLoading = (!isOffline && isLoadingOnyxValue(searchResultsMeta)) || searchResults?.data === undefined;
- const shouldShowEmptyState = !isLoading && isEmptyObject(searchResults?.data);
+ const isLoadingInitialItems = (!isOffline && isLoadingOnyxValue(searchResultsMeta)) || searchResults?.data === undefined;
+ const isLoadingMoreItems = !isLoadingInitialItems && searchResults?.search?.isLoading;
+ const shouldShowEmptyState = !isLoadingInitialItems && isEmptyObject(searchResults?.data);
- if (isLoading) {
+ if (isLoadingInitialItems) {
return ;
}
@@ -58,6 +60,14 @@ function Search({query, policyIDs}: SearchProps) {
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(query, reportID));
};
+ const fetchMoreResults = () => {
+ if (!searchResults?.search?.hasMoreResults || isLoadingInitialItems || isLoadingMoreItems) {
+ return;
+ }
+ const currentOffset = searchResults?.search?.offset ?? 0;
+ SearchActions.search(hash, query, currentOffset + CONST.SEARCH_RESULTS_PAGE_SIZE);
+ };
+
const type = SearchUtils.getSearchType(searchResults?.search);
if (type === undefined) {
@@ -80,6 +90,16 @@ function Search({query, policyIDs}: SearchProps) {
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
containerStyle={[styles.pv0]}
showScrollIndicator={false}
+ onEndReachedThreshold={0.75}
+ onEndReached={fetchMoreResults}
+ listFooterContent={
+ isLoadingMoreItems ? (
+
+ ) : undefined
+ }
/>
);
}
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index cb8d06097633..77b296740f2c 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -76,6 +76,8 @@ function BaseSelectionList(
sectionTitleStyles,
textInputAutoFocus = true,
shouldTextInputInterceptSwipe = false,
+ onEndReached = () => {},
+ onEndReachedThreshold,
}: BaseSelectionListProps,
ref: ForwardedRef,
) {
@@ -618,6 +620,8 @@ function BaseSelectionList(
onLayout={onSectionListLayout}
style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0}
ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance}
+ onEndReached={onEndReached}
+ onEndReachedThreshold={onEndReachedThreshold}
/>
{children}
>
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index 79e47e4aa4d7..0a4b0532b581 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -365,6 +365,17 @@ type BaseSelectionListProps = Partial & {
* When false, the list will render immediately and scroll to the bottom which works great for small lists.
*/
shouldHideListOnInitialRender?: boolean;
+
+ /** Called once when the scroll position gets within onEndReachedThreshold of the rendered content. */
+ onEndReached?: () => void;
+
+ /**
+ * How far from the end (in units of visible length of the list) the bottom edge of the
+ * list must be from the end of the content to trigger the `onEndReached` callback.
+ * Thus a value of 0.5 will trigger `onEndReached` when the end of the content is
+ * within half the visible length of the list.
+ */
+ onEndReachedThreshold?: number;
} & TRightHandSideComponent;
type SelectionListHandle = {
diff --git a/src/components/Skeletons/ItemListSkeletonView.tsx b/src/components/Skeletons/ItemListSkeletonView.tsx
index 00854471f2e3..5c46dbdddbfc 100644
--- a/src/components/Skeletons/ItemListSkeletonView.tsx
+++ b/src/components/Skeletons/ItemListSkeletonView.tsx
@@ -8,13 +8,14 @@ import CONST from '@src/CONST';
type ListItemSkeletonProps = {
shouldAnimate?: boolean;
renderSkeletonItem: (args: {itemIndex: number}) => React.ReactNode;
+ fixedNumItems?: number;
};
-function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem}: ListItemSkeletonProps) {
+function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem, fixedNumItems}: ListItemSkeletonProps) {
const theme = useTheme();
const themeStyles = useThemeStyles();
- const [numItems, setNumItems] = useState(0);
+ const [numItems, setNumItems] = useState(fixedNumItems ?? 0);
const skeletonViewItems = useMemo(() => {
const items = [];
for (let i = 0; i < numItems; i++) {
@@ -38,6 +39,10 @@ function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem}: ListIt
{
+ if (fixedNumItems) {
+ return;
+ }
+
const newNumItems = Math.ceil(event.nativeEvent.layout.height / CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT);
if (newNumItems === numItems) {
return;
diff --git a/src/components/Skeletons/TableListItemSkeleton.tsx b/src/components/Skeletons/TableListItemSkeleton.tsx
index d24268521100..4b2c214921d0 100644
--- a/src/components/Skeletons/TableListItemSkeleton.tsx
+++ b/src/components/Skeletons/TableListItemSkeleton.tsx
@@ -4,16 +4,18 @@ import ItemListSkeletonView from './ItemListSkeletonView';
type TableListItemSkeletonProps = {
shouldAnimate?: boolean;
+ fixedNumItems?: number;
};
const barHeight = '10';
const shortBarWidth = '40';
const longBarWidth = '120';
-function TableListItemSkeleton({shouldAnimate = true}: TableListItemSkeletonProps) {
+function TableListItemSkeleton({shouldAnimate = true, fixedNumItems}: TableListItemSkeletonProps) {
return (
(
<>