diff --git a/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx b/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx index 2bd328e21358..cd657ad42642 100644 --- a/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/TableContext.test.tsx @@ -100,6 +100,7 @@ const mockTableProviderProps = { onConnectData: jest.fn(), isInfiniteScrollEnabled: false, endOfData: false, + cachedTableData: [], }; // Test components @@ -208,6 +209,7 @@ describe("TableContext", () => { "onConnectData", "isInfiniteScrollEnabled", "endOfData", + "cachedTableData", ].sort(); const result = Object.keys( diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx index 2ce1f8501fc5..f1d5fd642a61 100644 --- a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/index.tsx @@ -13,6 +13,7 @@ interface InfiniteScrollBodyProps { const InfiniteScrollBodyComponent = React.forwardRef( (props: InfiniteScrollBodyProps, ref: Ref) => { const { + cachedTableData, endOfData, height, isLoading, @@ -20,6 +21,7 @@ const InfiniteScrollBodyComponent = React.forwardRef( pageSize, subPage: rows, tableSizes, + updatePageNo, } = useAppsmithTable(); const { onItemsRendered } = useInfiniteScroll({ @@ -28,6 +30,8 @@ const InfiniteScrollBodyComponent = React.forwardRef( loadMore: nextPageClick, isLoading, endOfData, + updatePageNo, + cachedTableData, }); const itemCount = useMemo( diff --git a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteScroll.tsx b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteScroll.tsx index 7200b9d735e7..b8a27b896146 100644 --- a/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteScroll.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/VirtualTable/InifiniteScrollBody/useInfiniteScroll.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback } from "react"; import { debounce } from "lodash"; import type { Row } from "react-table"; import type { ListOnItemsRenderedProps } from "react-window"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; export interface UseInfiniteScrollProps { loadMore: () => void; @@ -9,6 +10,8 @@ export interface UseInfiniteScrollProps { pageSize: number; isLoading: boolean; endOfData: boolean; + updatePageNo: (pageNo: number, event?: EventType) => void; + cachedTableData: Array>; } export interface UseInfiniteScrollReturn { @@ -16,27 +19,43 @@ export interface UseInfiniteScrollReturn { } export const useInfiniteScroll = ({ + cachedTableData, endOfData, isLoading, loadMore, pageSize, rows, + updatePageNo, }: UseInfiniteScrollProps): UseInfiniteScrollReturn => { const lastLoadedPageRef = useRef(1); const haveWeJustTriggeredLoadMoreRef = useRef(false); const hasLoadedSecondPageRef = useRef(false); - const lastRenderedRowInCurrentViewPortRef = useRef(0); - const currentPage = Math.ceil(rows.length / pageSize); + const lastRenderedRowAtWhichWeLoadedData = useRef(0); + const lastPageInTableDataset = Math.ceil(rows.length / pageSize); /** * We implement debouncing to avoid triggering unnecessary load more events, incorporating an additional timeout of 100 milliseconds to further prevent this. * There is also a ref that indicates whether a load more request has just been triggered, serving as a safety net to prevent multiple simultaneous requests. */ const debouncedLoadMore = useCallback( - debounce(() => { - if (!isLoading && !endOfData && !haveWeJustTriggeredLoadMoreRef.current) { + debounce((pageToLoad: number) => { + /** + * We need this `hasNextPageData` variable to verify that in scenarios where a query fails or the user goes offline momentarily, + * if we trigger a next page load, the meta property may update, but the table has not received any data. + * In such cases, we need to send another request to load the data if it has not been received yet. + */ + const hasNextPageData = + pageToLoad in cachedTableData && + Array.isArray(cachedTableData[pageToLoad]) && + cachedTableData[pageToLoad].length > 0; + const shouldLoad = !isLoading && !endOfData && !hasNextPageData; + const preRequisitesForLoadingMore = + shouldLoad && !haveWeJustTriggeredLoadMoreRef.current; + + if (preRequisitesForLoadingMore) { haveWeJustTriggeredLoadMoreRef.current = true; - loadMore(); + updatePageNo(pageToLoad, EventType.ON_NEXT_PAGE); + setTimeout(() => { haveWeJustTriggeredLoadMoreRef.current = false; }, 100); @@ -62,22 +81,28 @@ export const useInfiniteScroll = ({ */ const onItemsRendered = useCallback( (props: ListOnItemsRenderedProps) => { - const { visibleStopIndex } = props; - - const currentVisiblePage = Math.ceil(visibleStopIndex / pageSize); - const isInLastPage = currentVisiblePage === currentPage; + const { visibleStopIndex: lastRenderedRowInTheCurrentView } = props; + const currentVisiblePage = Math.ceil( + lastRenderedRowInTheCurrentView / pageSize, + ); + const isInLastPage = currentVisiblePage === lastPageInTableDataset; if ( isInLastPage && !isLoading && !endOfData && - visibleStopIndex > lastRenderedRowInCurrentViewPortRef.current + lastRenderedRowInTheCurrentView >= + lastRenderedRowAtWhichWeLoadedData.current ) { - lastRenderedRowInCurrentViewPortRef.current = visibleStopIndex; - debouncedLoadMore(); + if (lastRenderedRowAtWhichWeLoadedData.current !== rows.length) { + lastRenderedRowAtWhichWeLoadedData.current = + lastRenderedRowInTheCurrentView; + } + + debouncedLoadMore(currentVisiblePage + 1); } }, - [currentPage, isLoading, endOfData, pageSize, debouncedLoadMore], + [lastPageInTableDataset, isLoading, endOfData, pageSize, debouncedLoadMore], ); /** diff --git a/app/client/src/widgets/TableWidgetV2/component/header/actions/Actions.test.tsx b/app/client/src/widgets/TableWidgetV2/component/header/actions/Actions.test.tsx index 22ee19260787..9b3109ae6fad 100644 --- a/app/client/src/widgets/TableWidgetV2/component/header/actions/Actions.test.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/header/actions/Actions.test.tsx @@ -140,6 +140,7 @@ describe("TableWidget Actions Component", () => { onConnectData: jest.fn(), isLoading: false, endOfData: false, + cachedTableData: [], }; const renderWithTableProvider = (props: Partial) => { diff --git a/app/client/src/widgets/TableWidgetV2/component/index.tsx b/app/client/src/widgets/TableWidgetV2/component/index.tsx index 831c27e93ce4..069eb305b53d 100644 --- a/app/client/src/widgets/TableWidgetV2/component/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/index.tsx @@ -109,6 +109,7 @@ interface ReactTableComponentProps { onConnectData: () => void; isInfiniteScrollEnabled: boolean; endOfData: boolean; + cachedTableData: Array>; } function ReactTableComponent(props: ReactTableComponentProps) { @@ -243,6 +244,7 @@ function ReactTableComponent(props: ReactTableComponentProps) { borderRadius={props.borderRadius} borderWidth={borderWidth} boxShadow={props.boxShadow} + cachedTableData={props.cachedTableData} canFreezeColumn={canFreezeColumn} columnWidthMap={columnWidthMap} columns={columns} @@ -356,6 +358,7 @@ export default React.memo(ReactTableComponent, (prev, next) => { prev.disabledAddNewRowSave === next.disabledAddNewRowSave && prev.canFreezeColumn === next.canFreezeColumn && prev.showConnectDataOverlay === next.showConnectDataOverlay && - prev.isInfiniteScrollEnabled === next.isInfiniteScrollEnabled + prev.isInfiniteScrollEnabled === next.isInfiniteScrollEnabled && + equal(prev.cachedTableData, next.cachedTableData) ); }); diff --git a/app/client/src/widgets/TableWidgetV2/component/types.ts b/app/client/src/widgets/TableWidgetV2/component/types.ts index 4e8978327fb4..4f94f4aa1f54 100644 --- a/app/client/src/widgets/TableWidgetV2/component/types.ts +++ b/app/client/src/widgets/TableWidgetV2/component/types.ts @@ -81,6 +81,7 @@ export interface TableProps { onConnectData: () => void; isInfiniteScrollEnabled: boolean; endOfData: boolean; + cachedTableData: Array>; } export interface TableProviderProps extends TableProps { diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index c96d88ff24c4..c2607d239789 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -1302,6 +1302,7 @@ class TableWidgetV2 extends BaseWidget { borderRadius={this.props.borderRadius} borderWidth={this.props.borderWidth} boxShadow={this.props.boxShadow} + cachedTableData={this.props.cachedTableData} canFreezeColumn={this.props.canFreezeColumn} columnWidthMap={this.props.columnWidthMap} columns={tableColumns} @@ -3048,14 +3049,17 @@ class TableWidgetV2 extends BaseWidget { pushBatchMetaUpdates("cachedTableData", updatedCachedTableData); // The check (!!totalRecordsCount && processedTableData.length === totalRecordsCount) is added if the totalRecordsCount property is set then match the length with the processedTableData which has all flatted data from each page in a single array except the current tableData page i.e. [ ...array of page 1 data, ...array of page 2 data ]. Another 'or' check is if (tableData.length < pageSize) when totalRecordsCount is undefined. Table data has a single page data and if the data comes out to be lesser than the pageSize, it is assumed that the data is finished. - if ( - (!!totalRecordsCount && - processedTableData.length + tableData.length === totalRecordsCount) || - (!totalRecordsCount && tableData.length < pageSize) - ) { - pushBatchMetaUpdates("endOfData", true); - } else { - pushBatchMetaUpdates("endOfData", false); + if (window?.navigator?.onLine) { + if ( + (!!totalRecordsCount && + processedTableData.length + tableData.length === + totalRecordsCount) || + (!totalRecordsCount && tableData.length < pageSize) + ) { + pushBatchMetaUpdates("endOfData", true); + } else { + pushBatchMetaUpdates("endOfData", false); + } } if (shouldCommitBatchUpdates) {