-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat: Enhance infinite scrolling to use action button for load more data #39792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
8d2ffda
fb1e9df
7d3b851
2a5fda2
c868d6c
3d310ff
494efe2
be02036
1f8a80a
25d898d
2106627
0c21654
42733b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,15 @@ | ||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
| import { useEffect, useMemo, useRef, useState } from "react"; | ||
| import type { Row as ReactTableRowType } from "react-table"; | ||
| export interface UseInfiniteVirtualizationProps { | ||
| rows: ReactTableRowType<Record<string, unknown>>[]; | ||
| totalRecordsCount?: number; | ||
| isLoading: boolean; | ||
| loadMore: () => void; | ||
| pageSize: number; | ||
| } | ||
|
|
||
| export interface UseInfiniteVirtualizationReturn { | ||
| isItemLoaded: (index: number) => boolean; | ||
| itemCount: number; | ||
| loadMoreItems: (startIndex: number, stopIndex: number) => Promise<void>; | ||
| hasMoreData: boolean; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are going via this route, does it even need this variable? I mean, can this not be calculated by page number?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we can calculate with pageSize and rows, but putting it in the hook encapsulates that logic in a related scope. ALternatively we can remove it and have it in the InfiniteScrollBody, but we'd still have to pass it as props to BaseVirtualList. I prefer to encapsulate the logic for this inside the hook. |
||
| cachedRows: ReactTableRowType<Record<string, unknown>>[]; | ||
| } | ||
|
|
||
|
|
@@ -20,21 +18,14 @@ interface LoadedRowsCache { | |
| } | ||
|
|
||
| export const useInfiniteVirtualization = ({ | ||
| isLoading, | ||
| loadMore, | ||
| pageSize, | ||
| rows, | ||
| totalRecordsCount, | ||
| }: UseInfiniteVirtualizationProps): UseInfiniteVirtualizationReturn => { | ||
| const [loadedPages, setLoadedPages] = useState<LoadedRowsCache>({}); | ||
| const lastLoadedPageRef = useRef<number>(0); | ||
| const hasMoreDataRef = useRef<boolean>(true); // Track if more data is available | ||
|
|
||
| const maxPages = useMemo(() => { | ||
| if (!totalRecordsCount) return Infinity; | ||
|
|
||
| return Math.ceil(totalRecordsCount / pageSize); | ||
| }, [totalRecordsCount, pageSize]); | ||
| const hasMoreDataRef = useRef<boolean>(true); | ||
|
|
||
| useEffect(() => { | ||
| if (rows.length > 0) { | ||
|
|
@@ -52,6 +43,11 @@ export const useInfiniteVirtualization = ({ | |
| // If we got less than a full page, assume this is the last page | ||
| hasMoreDataRef.current = false; | ||
| } | ||
|
|
||
| // load another page in initial load if there is more data to load | ||
| if (cachedRows.length < pageSize * 2) { | ||
| loadMore(); | ||
| } | ||
| } else if (rows.length === 0 && lastLoadedPageRef.current > 0) { | ||
| // If no rows are returned and we've loaded at least one page, assume end of data | ||
| hasMoreDataRef.current = false; | ||
|
|
@@ -71,47 +67,18 @@ export const useInfiniteVirtualization = ({ | |
| return allRows; | ||
| }, [loadedPages]); | ||
|
|
||
| const isItemLoaded = useCallback( | ||
| (index: number) => { | ||
| const pageIndex = Math.floor(index / pageSize); | ||
|
|
||
| return ( | ||
| pageIndex >= maxPages || | ||
| pageIndex < lastLoadedPageRef.current || | ||
| !hasMoreDataRef.current | ||
| ); | ||
| }, | ||
| [pageSize, maxPages], | ||
| ); | ||
|
|
||
| const itemCount = useMemo(() => { | ||
| // If we know there's no more data, cap itemCount at cachedRows.length | ||
| if (!hasMoreDataRef.current) { | ||
| return cachedRows.length; | ||
| } | ||
|
|
||
| return totalRecordsCount || cachedRows.length; | ||
| return cachedRows.length; | ||
| }, [totalRecordsCount, cachedRows.length]); | ||
|
|
||
| const loadMoreItems = useCallback( | ||
| async (startIndex: number, stopIndex: number) => { | ||
| if (!isLoading && hasMoreDataRef.current) { | ||
| const targetPage = Math.floor(stopIndex / pageSize); | ||
|
|
||
| if (targetPage >= lastLoadedPageRef.current && targetPage < maxPages) { | ||
| loadMore(); | ||
| } | ||
| } | ||
|
|
||
| return Promise.resolve(); | ||
| }, | ||
| [isLoading, loadMore, pageSize, maxPages], | ||
| ); | ||
|
|
||
| return { | ||
| isItemLoaded, | ||
| itemCount, | ||
| loadMoreItems, | ||
| cachedRows, | ||
| hasMoreData: hasMoreDataRef.current, | ||
| }; | ||
| }; | ||
Uh oh!
There was an error while loading. Please reload this page.