Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d739bd1
chore: added caching layer for table widget data
Mar 13, 2025
b571cd1
fix: linting errors
Mar 13, 2025
072a4d1
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 18, 2025
e032afc
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 19, 2025
0ac4132
fix: merge from release, conflict resolve
Mar 24, 2025
b33d28f
chore: implement caching data
Mar 25, 2025
fb74765
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 25, 2025
c5b6570
chore: implement caching data
Mar 25, 2025
2c7a97e
chore: test fixes
Mar 25, 2025
33119f8
chore: test fixes
Mar 25, 2025
83cbb39
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 26, 2025
3527b8c
chore: test fixes
Mar 26, 2025
a8f30a8
chore: fix conditional caching data
Mar 28, 2025
332272d
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 28, 2025
f310a2b
chore: updated cached data on flag infinite scroll changes, on mounti…
Mar 28, 2025
e90d04a
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 28, 2025
9a48249
feat: added comments, changed condition for updating end of data
Mar 31, 2025
efead6b
feat: updated the end of data condition
Mar 31, 2025
8b59f5b
feat: updated the end of data condition
Mar 31, 2025
47b2775
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Mar 31, 2025
ca5081a
feat: updated the end of data condition
Mar 31, 2025
5629567
fix: added commitBatchMetaUpdates to push in the changes for infinite…
Apr 1, 2025
3812d2c
fix: updated has more data condition
Apr 1, 2025
51faddb
Merge branch 'release' of github.com:appsmithorg/appsmith into chore/…
Apr 1, 2025
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
@@ -1,11 +1,10 @@
import React, { type Ref } from "react";
import React, { useMemo, type Ref } from "react";
import { type ReactElementType } from "react-window";
import InfiniteLoader from "react-window-infinite-loader";
import type SimpleBar from "simplebar-react";
import { FixedInfiniteVirtualList } from "../../TableBodyCoreComponents/VirtualList";
import { useAppsmithTable } from "../../TableContext";
import { LoadingIndicator } from "../../LoadingIndicator";
import { useInfiniteVirtualization } from "./useInfiniteVirtualization";
import { useAppsmithTable } from "../../TableContext";

interface InfiniteScrollBodyProps {
innerElementType: ReactElementType;
Expand All @@ -15,28 +14,25 @@ const InfiniteScrollBodyComponent = React.forwardRef(
(props: InfiniteScrollBodyProps, ref: Ref<SimpleBar>) => {
const {
height,
isItemLoaded,
isLoading,
nextPageClick,
pageSize,
subPage: rows,
tableSizes,
totalRecordsCount,
} = useAppsmithTable();
const { cachedRows, isItemLoaded, itemCount, loadMoreItems } =
useInfiniteVirtualization({
rows,
totalRecordsCount,
isLoading,
loadMore: nextPageClick,
pageSize,
});

const itemCount = useMemo(() => {
return totalRecordsCount ?? rows.length;
}, [totalRecordsCount, rows]);

return (
<div className="simplebar-content-wrapper">
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={itemCount}
loadMoreItems={loadMoreItems}
loadMoreItems={nextPageClick}
minimumBatchSize={pageSize}
>
{({ onItemsRendered, ref: infiniteLoaderRef }) => (
Expand All @@ -48,7 +44,7 @@ const InfiniteScrollBodyComponent = React.forwardRef(
onItemsRendered={onItemsRendered}
outerRef={ref}
pageSize={pageSize}
rows={cachedRows}
rows={rows}
tableSizes={tableSizes}
/>
)}
Expand Down
20 changes: 17 additions & 3 deletions app/client/src/widgets/TableWidgetV2/component/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import equal from "fast-deep-equal/es6";
import { useCallback } from "react";
import type { EditableCell, TableVariant } from "../constants";
import { ColumnTypes } from "../constants";
import { useCachingVirtualization } from "./useCachingVirtualization";

export interface ColumnMenuOptionProps {
content: string | JSX.Element;
Expand Down Expand Up @@ -171,6 +172,18 @@ function ReactTableComponent(props: ReactTableComponentProps) {
width,
} = props;

const { cachedRows, isItemLoaded, isLoadingData, loadMoreNextPage } =
useCachingVirtualization({
data: tableData,
editableCell,
isLoading,
nextPageClick,
isInfiniteScrollEnabled,
pageSize,
totalRecordsCount,
isAddRowInProgress,
});

const sortTableColumn = useCallback(
(columnIndex: number, asc: boolean) => {
if (allowSorting) {
Expand Down Expand Up @@ -236,7 +249,7 @@ function ReactTableComponent(props: ReactTableComponentProps) {
columnWidthMap={columnWidthMap}
columns={columns}
compactMode={compactMode || CompactModeTypes.DEFAULT}
data={tableData}
data={cachedRows}
delimiter={delimiter}
disableDrag={memoziedDisableDrag}
disabledAddNewRowSave={disabledAddNewRowSave}
Expand All @@ -250,14 +263,15 @@ function ReactTableComponent(props: ReactTableComponentProps) {
height={height}
isAddRowInProgress={isAddRowInProgress}
isInfiniteScrollEnabled={isInfiniteScrollEnabled}
isLoading={isLoading}
isItemLoaded={isItemLoaded}
isLoading={isLoadingData}
isSortable={isSortable}
isVisibleDownload={isVisibleDownload}
isVisibleFilters={isVisibleFilters}
isVisiblePagination={isVisiblePagination}
isVisibleSearch={isVisibleSearch}
multiRowSelection={multiRowSelection}
nextPageClick={nextPageClick}
nextPageClick={loadMoreNextPage}
onAddNewRow={onAddNewRow}
onAddNewRowAction={onAddNewRowAction}
onBulkEditDiscard={onBulkEditDiscard}
Expand Down
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 @@ -80,6 +80,7 @@ export interface TableProps {
showConnectDataOverlay: boolean;
onConnectData: () => void;
isInfiniteScrollEnabled: boolean;
isItemLoaded: (index: number) => boolean;
}

export interface TableProviderProps extends TableProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { concat } from "lodash";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { EditableCell } from "../constants";

export interface UseCachingVirtualizationProps {
editableCell: EditableCell;
data: Array<Record<string, unknown>>;
isLoading: boolean;
isInfiniteScrollEnabled: boolean;
nextPageClick: () => void;
pageSize: number;
totalRecordsCount?: number;
isAddRowInProgress: boolean;
}

export interface UseCachingTableDataReturn {
isLoadingData: boolean;
cachedRows: Array<Record<string, unknown>>;
loadMoreNextPage: (
startIndex?: number,
stopIndex?: number,
) => Promise<void> | void;
isItemLoaded: (index: number) => boolean;
}

export const useCachingVirtualization = ({
data,
editableCell,
isAddRowInProgress,
isInfiniteScrollEnabled,
isLoading,
nextPageClick,
pageSize,
totalRecordsCount,
}: UseCachingVirtualizationProps): UseCachingTableDataReturn => {
const [cachedRows, setCachedRows] = useState<Array<Record<string, unknown>>>(
[],
);
const [isLoadingData, setIsLoadingData] = useState(false);
const lastLoadedPageRef = useRef<number>(0);
const hasMoreDataRef = useRef<boolean>(true);
const isAddRowInProgressRef = useRef<{
isAddRowInProgress: boolean;
addedNewRow: boolean;
}>({ isAddRowInProgress: false, addedNewRow: false });

const editableCellRef = useRef<{
isEditing: boolean;
editingCell?: EditableCell;
}>({ isEditing: false, editingCell: undefined });

const maxPages = useMemo(() => {
if (!totalRecordsCount) return Infinity;

return Math.ceil(totalRecordsCount / pageSize);
}, [totalRecordsCount, pageSize]);

const isItemLoaded = useCallback(
(index: number) => {
const pageIndex = Math.floor(index / pageSize);

return (
pageIndex >= maxPages ||
pageIndex < lastLoadedPageRef.current ||
!hasMoreDataRef.current
);
},
[pageSize, maxPages],
);

useEffect(() => {
if (isInfiniteScrollEnabled) {
setCachedRows([]);
}
}, [isInfiniteScrollEnabled]);

useEffect(() => {
isAddRowInProgressRef.current.isAddRowInProgress = isAddRowInProgress;
}, [isAddRowInProgress]);

useEffect(() => {
if (editableCell.index !== -1) {
editableCellRef.current.editingCell = editableCell;
editableCellRef.current.isEditing = true;
setCachedRows((e) => {
const newRows = [...e];

newRows[editableCell.index][editableCell.column] =
editableCell.inputValue;

return newRows;
});
} else {
editableCellRef.current.editingCell = undefined;
}
}, [editableCell]);

useEffect(() => {
if (isInfiniteScrollEnabled) {
if (isAddRowInProgressRef.current.isAddRowInProgress) {
if (isAddRowInProgressRef.current.addedNewRow) {
setCachedRows((e) => concat([data[0]], e.slice(1)));
} else {
setCachedRows((e) => concat([data[0]], e));
isAddRowInProgressRef.current.addedNewRow = true;
}

// TODO: If new rows are added, we would have to update the cached rows
return;
} else if (data.length > 0) {
if (isAddRowInProgressRef.current.addedNewRow) {
isAddRowInProgressRef.current.addedNewRow = false;
setCachedRows((e) => e.slice(1));
} else if (editableCellRef.current.isEditing) {
editableCellRef.current.isEditing = false;
} else {
const currentPageIndex = lastLoadedPageRef.current;

// Only increment if we got a full page or some data
if (data.length === pageSize) {
lastLoadedPageRef.current = currentPageIndex + 1;
} else if (data.length < pageSize && data.length > 0) {
// If we got less than a full page, assume this is the last page
hasMoreDataRef.current = false;
}

setCachedRows((e) => concat(e, data));
}
} else if (data.length === 0 && lastLoadedPageRef.current > 0) {
if (isAddRowInProgressRef.current.addedNewRow) {
isAddRowInProgressRef.current.addedNewRow = false;
setCachedRows((e) => concat(e.slice(1), data));
} else {
// If no rows are returned and we've loaded at least one page, assume end of data
hasMoreDataRef.current = false;
}
}
} else {
if (data.length > 0) {
setCachedRows(data);
}
}
}, [data, isInfiniteScrollEnabled]);

useEffect(() => {
setIsLoadingData(isLoading);
}, [isLoading]);

const loadMoreNextPage = useCallback(
async (startIndex?: number, stopIndex?: number) => {
if (isInfiniteScrollEnabled) {
if (!isLoadingData && hasMoreDataRef.current && !isAddRowInProgress) {
const targetPage = Math.floor(stopIndex! / pageSize);

if (
targetPage >= lastLoadedPageRef.current &&
targetPage < maxPages
) {
nextPageClick();
}
}

return Promise.resolve();
} else {
nextPageClick();
}
},
[
isLoadingData,
nextPageClick,
isInfiniteScrollEnabled,
pageSize,
maxPages,
isAddRowInProgress,
],
);

return {
cachedRows,
isItemLoaded,
isLoadingData,
loadMoreNextPage,
};
};
3 changes: 2 additions & 1 deletion app/client/src/widgets/TableWidgetV2/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,8 @@ class TableWidgetV2 extends BaseWidget<TableWidgetProps, WidgetState> {
*/
if (this.props.isAddRowInProgress) {
row = filteredTableData[rowIndex - 1];
originalIndex = rowIndex === 0 ? -1 : row[ORIGINAL_INDEX_KEY] ?? rowIndex;
originalIndex =
rowIndex === 0 ? -1 : row?.[ORIGINAL_INDEX_KEY] ?? rowIndex;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary space here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

row[ORIGINAL_INDEX_KEY] -> row?.[ORIGINAL_INDEX_KEY] due to this change, new space / line is added. cc @jacquesikot

} else {
row = filteredTableData[rowIndex];
originalIndex = row ? row[ORIGINAL_INDEX_KEY] ?? rowIndex : rowIndex;
Expand Down