Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const mockTableProviderProps = {
onConnectData: jest.fn(),
isInfiniteScrollEnabled: false,
endOfData: false,
cachedTableData: [],
};

// Test components
Expand Down Expand Up @@ -208,6 +209,7 @@ describe("TableContext", () => {
"onConnectData",
"isInfiniteScrollEnabled",
"endOfData",
"cachedTableData",
].sort();

const result = Object.keys(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ interface InfiniteScrollBodyProps {
const InfiniteScrollBodyComponent = React.forwardRef(
(props: InfiniteScrollBodyProps, ref: Ref<SimpleBar>) => {
const {
cachedTableData,
endOfData,
height,
isLoading,
nextPageClick,
pageSize,
subPage: rows,
tableSizes,
updatePageNo,
} = useAppsmithTable();

const { onItemsRendered } = useInfiniteScroll({
Expand All @@ -28,6 +30,8 @@ const InfiniteScrollBodyComponent = React.forwardRef(
loadMore: nextPageClick,
isLoading,
endOfData,
updatePageNo,
cachedTableData,
});

const itemCount = useMemo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,60 @@ 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;
rows: Row<Record<string, unknown>>[];
pageSize: number;
isLoading: boolean;
endOfData: boolean;
updatePageNo: (pageNo: number, event?: EventType) => void;
cachedTableData: Array<Record<string, unknown>>;
}

export interface UseInfiniteScrollReturn {
onItemsRendered: (props: ListOnItemsRenderedProps) => void;
}

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);
Expand All @@ -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],
);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ describe("TableWidget Actions Component", () => {
onConnectData: jest.fn(),
isLoading: false,
endOfData: false,
cachedTableData: [],
};

const renderWithTableProvider = (props: Partial<TableProviderProps>) => {
Expand Down
5 changes: 4 additions & 1 deletion app/client/src/widgets/TableWidgetV2/component/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ interface ReactTableComponentProps {
onConnectData: () => void;
isInfiniteScrollEnabled: boolean;
endOfData: boolean;
cachedTableData: Array<Record<string, unknown>>;
}

function ReactTableComponent(props: ReactTableComponentProps) {
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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)
);
});
1 change: 1 addition & 0 deletions app/client/src/widgets/TableWidgetV2/component/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface TableProps {
onConnectData: () => void;
isInfiniteScrollEnabled: boolean;
endOfData: boolean;
cachedTableData: Array<Record<string, unknown>>;
}

export interface TableProviderProps extends TableProps {
Expand Down
20 changes: 12 additions & 8 deletions app/client/src/widgets/TableWidgetV2/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,7 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
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}
Expand Down Expand Up @@ -3048,14 +3049,17 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
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) {
Expand Down
Loading