Skip to content
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

[XGrid] Make Infinite loading support rowCount #1715

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ada4c4d
Add support for detecting when virtual page changes
DanailH May 19, 2021
21196bd
Initial version
DanailH May 20, 2021
ddc90de
Remove commented out code
DanailH May 20, 2021
4c0a5b1
Add base skeleton row logic and styles
DanailH May 24, 2021
1f94832
Make new skeleton components public
DanailH May 24, 2021
e520dbc
Sort out loading on scroll and sorting
DanailH May 25, 2021
dd7af2c
Fix filter functionality
DanailH May 26, 2021
b841f96
rename loadRows option to getRows
DanailH May 27, 2021
4ab7cd0
resolve conflicts
DanailH May 27, 2021
97c86a8
Remove dead code and fix tests
DanailH May 27, 2021
802ff36
Fix styles
DanailH May 27, 2021
9dd3579
Fix server pagination
DanailH May 27, 2021
8bbbe71
Rename methods and decouple api method from grid option
DanailH May 31, 2021
6222f1e
PR comments
DanailH May 31, 2021
8fc923e
Fix trailing rows issue
DanailH May 31, 2021
369cb68
Add tests
DanailH Jun 1, 2021
60a7adc
Remove code
DanailH Jun 1, 2021
11d936a
Finished tests
DanailH Jun 2, 2021
f9f2ab1
Add docs
DanailH Jun 2, 2021
59aa187
Load one page more
DanailH Jun 9, 2021
658879f
add story
DanailH Jun 9, 2021
f81a1cf
Update docs/src/pages/components/data-grid/rows/rows.md
DanailH Jun 15, 2021
4290995
PR comments
DanailH Jun 15, 2021
323f10a
Merge branch 'feature/DataGrid-1247-rowCount-infite-loader' of github…
DanailH Jun 15, 2021
02b02de
resolve conflict
DanailH Jun 15, 2021
b481127
run prettier
DanailH Jun 15, 2021
42f0830
format docs api
DanailH Jun 15, 2021
c08eb4b
Fix docs
DanailH Jun 16, 2021
2007edd
Fix conflicts
DanailH Jun 16, 2021
cdcdfee
Resolve conflicts
DanailH Jun 22, 2021
f96f635
Add infniteLoaderMode option
DanailH Jun 22, 2021
828a186
Fix tests
DanailH Jun 22, 2021
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
5 changes: 3 additions & 2 deletions packages/grid/_modules_/grid/components/GridRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const GridRow = (props: GridRowProps) => {
const apiRef = React.useContext(GridApiContext);
const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector);
const options = useGridSelector(apiRef, optionsSelector);
const rowId = id || `key-${Math.floor(Math.random() * 1000)}`;

const publish = React.useCallback(
(eventName: string) => (event: React.MouseEvent) => {
Expand Down Expand Up @@ -69,8 +70,8 @@ export const GridRow = (props: GridRowProps) => {

return (
<div
key={id}
data-id={id}
key={rowId}
data-id={rowId}
data-rowindex={rowIndex}
role="row"
className={cssClasses}
Expand Down
37 changes: 21 additions & 16 deletions packages/grid/_modules_/grid/components/GridViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>(
renderState.renderContext.firstRowIdx,
renderState.renderContext.lastRowIdx!,
);

return renderedRows.map(([id, row], idx) => (
<GridRow
className={
Expand All @@ -63,22 +64,26 @@ export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>(
rowIndex={renderState.renderContext!.firstRowIdx! + idx}
>
<GridEmptyCell width={renderState.renderContext!.leftEmptyWidth} height={rowHeight} />
<GridRowCells
columns={visibleColumns}
row={row}
id={id}
firstColIdx={renderState.renderContext!.firstColIdx!}
lastColIdx={renderState.renderContext!.lastColIdx!}
hasScrollX={scrollBarState.hasScrollX}
hasScrollY={scrollBarState.hasScrollY}
showCellRightBorder={!!options.showCellRightBorder}
extendRowFullWidth={!options.disableExtendRowFullWidth}
rowIndex={renderState.renderContext!.firstRowIdx! + idx}
cellFocus={cellFocus}
cellTabIndex={cellTabIndex}
isSelected={selectionState[id] !== undefined}
editRowState={editRowsState[id]}
/>
{id.toString().indexOf('null-') === 0 ? (
<div>EMPTY</div>
) : (
<GridRowCells
columns={visibleColumns}
row={row}
id={id}
firstColIdx={renderState.renderContext!.firstColIdx!}
lastColIdx={renderState.renderContext!.lastColIdx!}
hasScrollX={scrollBarState.hasScrollX}
hasScrollY={scrollBarState.hasScrollY}
showCellRightBorder={!!options.showCellRightBorder}
extendRowFullWidth={!options.disableExtendRowFullWidth}
rowIndex={renderState.renderContext!.firstRowIdx! + idx}
cellFocus={cellFocus}
cellTabIndex={cellTabIndex}
isSelected={selectionState[id] !== undefined}
editRowState={editRowsState[id]}
/>
)}
<GridEmptyCell width={renderState.renderContext!.rightEmptyWidth} height={rowHeight} />
</GridRow>
));
Expand Down
6 changes: 6 additions & 0 deletions packages/grid/_modules_/grid/constants/eventsConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,9 @@ export const GRID_STATE_CHANGE = 'stateChange';
* @event
*/
export const GRID_COLUMN_VISIBILITY_CHANGE = 'columnVisibilityChange';

/**
* Fired when the virtual page changes. Called with a [[GridVirtualPageChangeParams]] object.
* @event
*/
export const GRID_VIRTUAL_PAGE_CHANGE = 'virtualPageChange';
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function buildCSV(
selectedRows: Record<string, GridRowId>,
getCellValue: (id: GridRowId, field: string) => GridCellValue,
): string {
let rowIds = [...rows.keys()];
let rowIds = [...rows.keys()].filter((id) => id.toString().indexOf('null-') !== 0);
const selectedRowIds = Object.keys(selectedRows);

if (selectedRowIds.length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export const useGridFilter = (apiRef: GridApiRef, rowsProp: GridRowsProp): void
const rows = sortedGridRowsSelector(state);

rows.forEach((row: GridRowModel, id: GridRowId) => {
if (id.toString().indexOf('null-') === 0) {
return;
}

const params = apiRef.current.getCellParams(id, filterItem.columnField!);

const isShown = applyFilterOnRow(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@ import * as React from 'react';
import { optionsSelector } from '../../utils/optionsSelector';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { useGridSelector } from '../core/useGridSelector';
import { GRID_ROWS_SCROLL, GRID_ROWS_SCROLL_END } from '../../../constants/eventsConstants';
import {
GRID_ROWS_SCROLL,
GRID_ROWS_SCROLL_END,
GRID_VIRTUAL_PAGE_CHANGE,
} from '../../../constants/eventsConstants';
import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector';
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../root/useGridApiEventHandler';
import { GridRowScrollEndParams } from '../../../models/params/gridRowScrollEndParams';
import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector';
import { GridVirtualPageChangeParams } from '../../../models/params/gridVirtualPageChangeParams';
import { useLogger } from '../../utils/useLogger';

export const useGridInfiniteLoader = (apiRef: GridApiRef): void => {
const logger = useLogger('useGridInfiniteLoader');
const options = useGridSelector(apiRef, optionsSelector);
const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector);
const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector);
const isInScrollBottomArea = React.useRef<boolean>(false);

const handleGridScroll = React.useCallback(() => {
logger.debug('Checking if scroll position reached bottom');

if (!containerSizes) {
return;
}

const scrollPosition = apiRef.current.getScrollPosition();

const scrollPositionBottom =
scrollPosition.top + containerSizes.windowSizes.height + options.scrollEndThreshold;

Expand All @@ -29,7 +39,8 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => {

if (
scrollPositionBottom >= containerSizes.dataContainerSizes.height &&
!isInScrollBottomArea.current
!isInScrollBottomArea.current &&
!options.rowCount
) {
const rowScrollEndParam: GridRowScrollEndParams = {
api: apiRef,
Expand All @@ -40,8 +51,44 @@ export const useGridInfiniteLoader = (apiRef: GridApiRef): void => {
apiRef.current.publishEvent(GRID_ROWS_SCROLL_END, rowScrollEndParam);
isInScrollBottomArea.current = true;
}
}, [options, containerSizes, apiRef, visibleColumns]);
}, [logger, options, containerSizes, apiRef, visibleColumns]);

const handleGridVirtualPageChange = React.useCallback(
(params: GridVirtualPageChangeParams) => {
logger.debug('Virtual page changed');

if (!containerSizes || !options.loadRows) {
return;
}

const state = apiRef.current.getState();
const newRowsBatchStartIndex = (params.nextPage + 1) * containerSizes.viewportPageSize;
const toBeLoadedRange: any = [...state.rows.allRows].splice(
newRowsBatchStartIndex,
containerSizes.viewportPageSize,
);

if (!toBeLoadedRange.includes(null)) {
return;
}

const newRowsBatch = options.loadRows({
startIndex: newRowsBatchStartIndex,
viewportPageSize: containerSizes.viewportPageSize,
});

if (newRowsBatch.length) {
apiRef.current.loadRows(
newRowsBatchStartIndex,
containerSizes.viewportPageSize,
newRowsBatch,
);
}
},
[logger, options, containerSizes, apiRef],
);

useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll);
useGridApiEventHandler(apiRef, GRID_VIRTUAL_PAGE_CHANGE, handleGridVirtualPageChange);
useGridApiOptionHandler(apiRef, GRID_ROWS_SCROLL_END, options.onRowsScrollEnd);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ export interface InternalGridRowsState {
totalRowCount: number;
}

export const getInitialGridRowState: () => InternalGridRowsState = () => ({
export const getInitialGridRowState: (rowCount?: number) => InternalGridRowsState = (
rowsCount = 0,
) => ({
idRowsLookup: {},
allRows: [],
allRows: new Array(rowsCount).fill(null),
totalRowCount: 0,
});
37 changes: 34 additions & 3 deletions packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export function convertGridRowsPropToState(
rowIdGetter?: GridRowIdGetter,
): InternalGridRowsState {
const state: InternalGridRowsState = {
...getInitialGridRowState(),
...getInitialGridRowState(totalRowCount),
totalRowCount: totalRowCount && totalRowCount > rows.length ? totalRowCount : rows.length,
};

rows.forEach((rowData) => {
rows.forEach((rowData, index) => {
const id = getGridRowId(rowData, rowIdGetter);
state.allRows.push(id);
state.allRows[index] = id;
state.idRowsLookup[id] = rowData;
});

Expand Down Expand Up @@ -114,6 +114,36 @@ export const useGridRows = (
[apiRef],
);

const loadRows = React.useCallback(
(startIndex: number, pageSize: number, newRows: GridRowModel[]) => {
logger.debug(`Loading rows from index:${startIndex} to index:${startIndex + pageSize}`);

const newRowsToState = convertGridRowsPropToState(newRows, newRows.length, getRowIdProp);

setGridState((state) => {
const allRowsUpdated = state.rows.allRows.map((row, index) => {
if (index >= startIndex && index < startIndex + pageSize) {
return newRowsToState.allRows[index - startIndex];
}
return row;
});

internalRowsState.current = {
allRows: allRowsUpdated,
idRowsLookup: { ...state.rows.idRowsLookup, ...newRowsToState.idRowsLookup },
totalRowCount: state.rows.totalRowCount,
};

return { ...state, rows: internalRowsState.current };
});
Copy link
Member

@oliviertassinari oliviertassinari May 23, 2021

Choose a reason for hiding this comment

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

This section seems weird. Why not use the apiRef.current.updateRows() API? It supports additions.

Copy link
Member Author

@DanailH DanailH May 24, 2021

Choose a reason for hiding this comment

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

Maybe I didn't understand the updateRows but from what I can see it supports update, delete, and adding new rows but it can't replace existing rows, which is what I need here. but now thinking about it maybe it would be better to extend updateRows 🤔. The only thing missing is the start and end indexes from which the update needs to happen.


forceUpdate();
apiRef.current.updateViewport();
apiRef.current.applySorting();
},
[logger, apiRef, getRowIdProp, setGridState, forceUpdate],
);

const setRows = React.useCallback(
(allNewRows: GridRowModel[]) => {
logger.debug(`updating all rows, new length ${allNewRows.length}`);
Expand Down Expand Up @@ -222,6 +252,7 @@ export const useGridRows = (
getAllRowIds,
setRows,
updateRows,
loadRows,
};
useGridApiMethod(apiRef, rowApi, 'GridRowApi');
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export const sortedGridRowsSelector = createSelector<
(sortedIds: GridRowId[], idRowsLookup: GridRowsLookup) => {
const map = new Map<GridRowId, GridRowModel>();
sortedIds.forEach((id) => {
map.set(id, idRowsLookup[id]);
const normalizeId = id !== null ? id : `null-${Math.floor(Math.random() * 1000)}`;
map.set(normalizeId, idRowsLookup[id]);
});
return map;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,21 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => {
if (sortModel.length > 0) {
const comparatorList = buildComparatorList(sortModel);
logger.debug('Sorting rows with ', sortModel);
const rowIdsLength = rowIds.length;
sorted = rowIds
.filter((id) => id !== null)
.map((id) => {
return comparatorList.map((colComparator) => {
return getSortCellParams(id, colComparator.field);
});
})
.sort(comparatorListAggregate(comparatorList))
.map((field) => field[0].id);

if (rowIdsLength !== sorted.length) {
const NullArray = new Array(rowIdsLength - sorted.length).fill(null);
sorted = sorted.concat(NullArray);
}
}

setGridState((oldState) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
GRID_RESIZE,
GRID_NATIVE_SCROLL,
GRID_ROWS_SCROLL,
GRID_VIRTUAL_PAGE_CHANGE,
} from '../../../constants/eventsConstants';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { GridVirtualizationApi } from '../../../models/api/gridVirtualizationApi';
Expand Down Expand Up @@ -154,6 +155,12 @@ export const useGridVirtualRows = (
if (containerProps.isVirtualized && page !== nextPage) {
setRenderingState({ virtualPage: nextPage });
logger.debug(`Changing page from ${page} to ${nextPage}`);

apiRef.current.publishEvent(GRID_VIRTUAL_PAGE_CHANGE, {
currentPage: page,
nextPage,
api: apiRef,
});
Comment on lines +154 to +159
Copy link
Member

@oliviertassinari oliviertassinari Jun 14, 2021

Choose a reason for hiding this comment

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

This seems to be an implementation detail of the virtualization. I don't think that we should expose it.

Suggested change
apiRef.current.publishEvent(GRID_VIRTUAL_PAGE_CHANGE, {
currentPage: page,
nextPage,
api: apiRef,
});

Instead, prefer listening to the viewport or anything that is resilient to a more standard virtualization implementation like in react-window, react-virtuoso, react-virtual.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can call the API here directly but that would deviate from the established pattern. I added that event because of it. We can not document the event and keep it private?

Copy link
Member

@oliviertassinari oliviertassinari Jun 16, 2021

Choose a reason for hiding this comment

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

Could we listen to the scroll event instead? Or anything close to https://ag-grid.com/react-grid/viewport/. But avoid at ALL cost to depend on any virtualization implementation detail? Based on #1911 and #1903, I think that there is a nonnegligible chance that this "page" notion won't stick around.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can listen to any of the scroll events we currently have but I would still need to calculate where the last currently virtualized row is and when the new one comes in.
I know about the current limitations of the virtualization, we discussed them in the last meeting with @dtassone and @m4theushw (the scrollToIndex jump problem). The way I see it even if the logic of the virtualization changes would still be a notion of what is currently not virtualized so a potential refactoring would not be much. That is the only part that potentially wound need to change.

requireRerender = true;
} else {
if (!containerProps.isVirtualized && page > 0) {
Expand Down
7 changes: 7 additions & 0 deletions packages/grid/_modules_/grid/models/api/gridRowApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ export interface GridRowApi {
* @param id
*/
getRowFromId: (id: GridRowId) => GridRowModel;
/**
* Loads a new subset of Rows.
* @param startIndex
* @param pageSize
* @param rows
*/
loadRows: (startIndex: number, pageSize: number, rows: GridRowModel[]) => void;
}
4 changes: 4 additions & 0 deletions packages/grid/_modules_/grid/models/gridOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ export interface GridOptions {
* Callback fired when a cell is rendered, returns true if the cell is editable.
*/
isCellEditable?: (params: GridCellParams) => boolean;
/**
*
*/
loadRows?: (params) => any;
/**
* Set the locale text of the grid.
* You can find all the translation keys supported in [the source](https://github.com/mui-org/material-ui-x/blob/HEAD/packages/grid/_modules_/grid/constants/localeTextConstants.ts) in the GitHub repository.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Object passed as parameter of the virtual page change event.
*/
export interface GridVirtualPageChangeParams {
/**
* The current page.
*/
currentPage: number;
/**
* The next page.
*/
nextPage: number;
/**
* Api that let you manipulate the grid.
*/
api: any;
}
Loading