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

[DataGrid] Add onRowsScrollEnd to support infinite loading #1199

Merged
merged 38 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c3c97d9
WIP
DanailH Mar 9, 2021
848ba04
Finish the features logic
DanailH Mar 9, 2021
ef052c4
Fix formmating
DanailH Mar 9, 2021
afd4715
Merge branch 'master' of github.com:mui-org/material-ui-x into featur…
DanailH Mar 10, 2021
0fa88ca
Add storybook demo
DanailH Mar 10, 2021
d1d177c
Review comments
DanailH Mar 10, 2021
8e1349b
Update useDemoData hook
DanailH Mar 10, 2021
82927cb
refactor
DanailH Mar 10, 2021
68ea707
Move size selectors to separate file
DanailH Mar 10, 2021
e7f0482
Rework events
DanailH Mar 11, 2021
b25717e
move container size selectors
DanailH Mar 11, 2021
2052d3c
Add params to the callback
DanailH Mar 11, 2021
1d02608
Revert useDemoData changes
DanailH Mar 11, 2021
2fe73eb
Add test and docs and prep the PR for review
DanailH Mar 11, 2021
0395181
rename events
DanailH Mar 11, 2021
a3635c0
Resolve conflicts
DanailH Mar 12, 2021
d5d94b4
Update docs/src/pages/components/data-grid/rendering/rendering.md
DanailH Mar 12, 2021
2a0dd74
PR review comments
DanailH Mar 12, 2021
8004dea
resolve conflicts
DanailH Mar 12, 2021
02c22c2
Move Infinite loading docs under rows
DanailH Mar 12, 2021
1c5991d
Use XGrid in demos
DanailH Mar 12, 2021
ae1dc1c
Update example code
DanailH Mar 12, 2021
22e0167
add new scroll param prop
DanailH Mar 12, 2021
657fbd8
make the feature pro
DanailH Mar 12, 2021
246eece
Flatten if statement
DanailH Mar 13, 2021
24de8a6
Update packages/grid/data-grid/src/DataGrid.tsx
DanailH Mar 15, 2021
3bd15dd
Update packages/grid/_modules_/grid/constants/eventsConstants.ts
DanailH Mar 15, 2021
3eac46f
Fix pr comments
DanailH Mar 15, 2021
0a1b799
Merge branch 'feature/DataGrid-404-infinite-loading' of github.com:Da…
DanailH Mar 15, 2021
182456d
Polish demos
DanailH Mar 16, 2021
f228c90
Sort out grid props
DanailH Mar 16, 2021
18c24d6
Use Danail tip
oliviertassinari Mar 16, 2021
8952a1b
remove console.log
oliviertassinari Mar 16, 2021
1d655e8
remove absoluteTop and depend on realScroll
DanailH Mar 17, 2021
9af4f66
add additional test
DanailH Mar 17, 2021
cf50ca5
add selector
DanailH Mar 17, 2021
84de4e3
Load the correct package
DanailH Mar 17, 2021
6ba992e
Add new api
DanailH Mar 17, 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
2 changes: 2 additions & 0 deletions docs/pages/api-docs/x-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { XGrid } from '@material-ui/x-grid';
| <span class="prop-name">onRowEnter</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse enter comes from a row container element. |
| <span class="prop-name">onRowLeave</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse leave event comes from a row container element. |
| <span class="prop-name">onRowSelected</span> | <span class="prop-type">(param: GridRowSelectedParams) => void</span> | | Callback fired when one row is selected. |
| <span class="prop-name">onRowsScrollEnd</span> | <span class="prop-type">(param: GridRowScrollEndParams) => void</span> | | Callback fired when scrolling to the bottom of the grid viewport. |
| <span class="prop-name">onSelectionModelChange</span> | <span class="prop-type">(param: GridSelectionModelChangeParams) => void</span> | | Callback fired when the selection state of one or multiple rows changes. |
| <span class="prop-name">onSortModelChange</span> | <span class="prop-type">(param: GridSortModelParams) => void</span> | | Callback fired when the sort model changes before a column is sorted. |
| <span class="prop-name">page</span> | <span class="prop-type">number</span> | 1 | Set the current page. |
Expand All @@ -83,6 +84,7 @@ import { XGrid } from '@material-ui/x-grid';
| <span class="prop-name">rowHeight</span> | <span class="prop-type">number</span> | 52 | Set the height in pixel of a row in the grid. |
| <span class="prop-name">rowsPerPageOptions</span> | <span class="prop-type">number[]</span> | [25, 50, 100] | Select the pageSize dynamically using the component UI. |
| <span class="prop-name">scrollbarSize</span> | <span class="prop-type">number</span> | 15 | Set the height/width of the grid inner scrollbar. |
| <span class="prop-name">scrollEndThreshold</span> | <span class="prop-type">number</span> | 80 | Set the area at the bottom of the grid viewport where onRowsScrollEnd is called. |
| <span class="prop-name">selectionModel</span> | <span class="prop-type">GridSelectionModel</span> | | Set the selection model of the grid. |
| <span class="prop-name">showCellRightBorder</span> | <span class="prop-type">boolean</span> | false | If `true`, the right border of the cells are displayed. |
| <span class="prop-name">showColumnRightBorder</span> | <span class="prop-type">boolean</span> | false | If `true`, the right border of the column headers are displayed. |
Expand Down
78 changes: 78 additions & 0 deletions docs/src/pages/components/data-grid/rows/InfiniteLoadingGrid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react';
import { XGrid, GridOverlay } from '@material-ui/x-grid';
import {
useDemoData,
getRealData,
getCommodityColumns,
} from '@material-ui/x-grid-data-generator';
import LinearProgress from '@material-ui/core/LinearProgress';

const MAX_ROW_LENGTH = 500;

async function sleep(duration) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}

function CustomLoadingOverlay() {
return (
<GridOverlay>
<div style={{ position: 'absolute', top: 0, width: '100%' }}>
<LinearProgress />
</div>
</GridOverlay>
);
}

export default function InfiniteLoadingGrid() {
const [loading, setLoading] = React.useState(false);
const [loadedRows, setLoadedRows] = React.useState([]);
const mounted = React.useRef(true);
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 20,
maxColumns: 6,
});

const loadServerRows = async (newRowLength) => {
setLoading(true);
const newData = await getRealData(newRowLength, getCommodityColumns());
// Simulate network throttle
await sleep(Math.random() * 500 + 100);

if (mounted.current) {
setLoading(false);
setLoadedRows(loadedRows.concat(newData.rows));
}
};

const handleOnRowsScrollEnd = (params) => {
if (loadedRows.length <= MAX_ROW_LENGTH) {
loadServerRows(params.viewportPageSize);
}
};

React.useEffect(() => {
return () => {
mounted.current = false;
};
}, []);

return (
<div style={{ height: 400, width: '100%' }}>
<XGrid
Copy link
Member

@dtassone dtassone Mar 16, 2021

Choose a reason for hiding this comment

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

there is also a rowLength prop that you can set so the footer shows the correct amount of rows and the scrollbar should size accordingly

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 don't see a rowLength prop on the grid. The footer is showing the total rows currently.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry
rowCount?: number;

Copy link
Member

Choose a reason for hiding this comment

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

why don't you pass the realScroll state in the event params.
The event is fired from updateViewport where all the required params are, so you could just change the event payload and pass the real scroll params as well as the rendering scroll params.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok to summarise our call from yesterday - I will follow up on part for the rowCount and the functionality where all the rows are allocated in advance and a callback is called requestion for a specific range of rows to be loaded. I still think that the feature as it now brings value and the extra mile of making it work in combination with rowCount can be done afterward.

PS: I removed my other comment -> result -> fixed.

Copy link
Member

Choose a reason for hiding this comment

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

Much better without the absoluteTop 👍 .
Ok, let's clean up this one a bit more and but scope this PR to the current feature.
Please open an issue describing the rest of the feature with the rowCount. Those needs to work together.

Copy link
Member Author

Choose a reason for hiding this comment

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

Here is the ticket -> #1247

{...data}
rows={data.rows.concat(loadedRows)}
loading={loading}
hideFooterPagination
onRowsScrollEnd={handleOnRowsScrollEnd}
components={{
LoadingOverlay: CustomLoadingOverlay,
}}
/>
</div>
);
}
78 changes: 78 additions & 0 deletions docs/src/pages/components/data-grid/rows/InfiniteLoadingGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import * as React from 'react';
import { XGrid, GridOverlay } from '@material-ui/x-grid';
import {
useDemoData,
getRealData,
getCommodityColumns,
} from '@material-ui/x-grid-data-generator';
import LinearProgress from '@material-ui/core/LinearProgress';

const MAX_ROW_LENGTH = 500;

async function sleep(duration: number) {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, duration);
});
}

function CustomLoadingOverlay() {
return (
<GridOverlay>
<div style={{ position: 'absolute', top: 0, width: '100%' }}>
<LinearProgress />
</div>
</GridOverlay>
);
}

export default function InfiniteLoadingGrid() {
const [loading, setLoading] = React.useState(false);
const [loadedRows, setLoadedRows] = React.useState<any>([]);
const mounted = React.useRef(true);
const { data } = useDemoData({
dataSet: 'Commodity',
rowLength: 20,
maxColumns: 6,
});

const loadServerRows = async (newRowLength) => {
setLoading(true);
const newData = await getRealData(newRowLength, getCommodityColumns());
// Simulate network throttle
await sleep(Math.random() * 500 + 100);

if (mounted.current) {
setLoading(false);
setLoadedRows(loadedRows.concat(newData.rows));
}
};

const handleOnRowsScrollEnd = (params) => {
if (loadedRows.length <= MAX_ROW_LENGTH) {
loadServerRows(params.viewportPageSize);
}
};

React.useEffect(() => {
return () => {
mounted.current = false;
};
}, []);

return (
<div style={{ height: 400, width: '100%' }}>
<XGrid
{...data}
rows={data.rows.concat(loadedRows)}
loading={loading}
hideFooterPagination
onRowsScrollEnd={handleOnRowsScrollEnd}
components={{
LoadingOverlay: CustomLoadingOverlay,
}}
/>
</div>
);
}
8 changes: 8 additions & 0 deletions docs/src/pages/components/data-grid/rows/rows.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ It replaces the previous values. This approach has some drawbacks:
- You need to provide all the rows.
- You might create a performance bottleneck when preparing the rows array to provide to the grid.

### Infinite loading [<span class="pro"></span>](https://material-ui.com/store/items/material-ui-pro/)

The grid provides a `onRowsScrollEnd` prop that can be used to load additional rows when the scroll reaches the bottom of the viewport area.

In addition, the area in which the callback provided to the `onRowsScrollEnd` is called can be changed using `scrollEndThreshold`.

{{"demo": "pages/components/data-grid/rows/InfiniteLoadingGrid.js", "bg": "inline"}}

### apiRef [<span class="pro"></span>](https://material-ui.com/store/items/material-ui-pro/)

The second way to update rows is to use the apiRef.
Expand Down
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { GridApiContext } from './components/GridApiContext';
import { useGridFilter } from './hooks/features/filter/useGridFilter';
import { useLocaleText } from './hooks/features/localeText/useLocaleText';
import { useGridCsvExport } from './hooks/features/export';
import { useGridInfiniteLoader } from './hooks/features/infiniteLoader';

export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps>(
function GridComponent(props, ref) {
Expand Down Expand Up @@ -92,6 +93,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
useGridColumnResize(columnsHeaderRef, apiRef);
useGridPagination(apiRef);
useGridCsvExport(apiRef);
useGridInfiniteLoader(apiRef);

const components = useGridComponents(props.components, props.componentsProps, apiRef);
useStateProp(apiRef, props.state);
Expand Down
4 changes: 2 additions & 2 deletions packages/grid/_modules_/grid/components/GridScrollArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import {
GRID_COL_REORDER_START,
GRID_COL_REORDER_STOP,
GRID_SCROLLING,
GRID_ROWS_SCROLL,
} from '../constants/eventsConstants';
import { useGridApiEventHandler } from '../hooks/root/useGridApiEventHandler';
import { GridApiRef } from '../models/api/gridApiRef';
Expand Down Expand Up @@ -68,7 +68,7 @@ export const GridScrollArea = React.memo(function GridScrollArea(props: ScrollAr
setDragging((prevdragging) => !prevdragging);
}, []);

useGridApiEventHandler(api as GridApiRef, GRID_SCROLLING, handleScrolling);
useGridApiEventHandler(api as GridApiRef, GRID_ROWS_SCROLL, handleScrolling);
useGridApiEventHandler(api as GridApiRef, GRID_COL_REORDER_START, toggleDragging);
useGridApiEventHandler(api as GridApiRef, GRID_COL_REORDER_STOP, toggleDragging);

Expand Down
10 changes: 5 additions & 5 deletions packages/grid/_modules_/grid/components/GridViewport.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { visibleGridColumnsSelector } from '../hooks/features/columns/gridColumnsSelector';
import { GridState } from '../hooks/features/core/gridState';
import { useGridSelector } from '../hooks/features/core/useGridSelector';
import { gridDensityRowHeightSelector } from '../hooks/features/density/densitySelector';
import { visibleSortedGridRowsSelector } from '../hooks/features/filter/gridFilterSelector';
Expand All @@ -15,13 +14,14 @@ import { GridRenderingZone } from './GridRenderingZone';
import { GridRow } from './GridRow';
import { GridRowCells } from './GridRowCells';
import { GridStickyContainer } from './GridStickyContainer';
import {
gridContainerSizesSelector,
gridViewportSizesSelector,
gridScrollBarSizeSelector,
} from '../hooks/root/gridContainerSizesSelector';

type ViewportType = React.ForwardRefExoticComponent<React.RefAttributes<HTMLDivElement>>;

export const gridContainerSizesSelector = (state: GridState) => state.containerSizes;
export const gridViewportSizesSelector = (state: GridState) => state.viewportSizes;
export const gridScrollBarSizeSelector = (state: GridState) => state.scrollBar;

export const GridViewport: ViewportType = React.forwardRef<HTMLDivElement, {}>(
(props, renderingZoneRef) => {
const apiRef = React.useContext(GridApiContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { optionsSelector } from '../../hooks/utils/optionsSelector';
import { GridApiContext } from '../GridApiContext';
import { GridEmptyCell } from '../GridEmptyCell';
import { GridScrollArea } from '../GridScrollArea';
import { gridContainerSizesSelector } from '../GridViewport';
import { GridColumnHeadersItemCollection } from './GridColumnHeadersItemCollection';
import { gridDensityHeaderHeightSelector } from '../../hooks/features/density/densitySelector';
import { gridContainerSizesSelector } from '../../hooks/root/gridContainerSizesSelector';

export const gridScrollbarStateSelector = (state: GridState) => state.scrollBar;

Expand Down
5 changes: 3 additions & 2 deletions packages/grid/_modules_/grid/constants/eventsConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const GRID_RESIZE = 'resize';
export const GRID_FOCUS_OUT = 'focusout';
export const GRID_KEYDOWN = 'keydown';
export const GRID_KEYUP = 'keyup';
export const GRID_SCROLL = 'scroll';
export const GRID_NATIVE_SCROLL = 'scroll';
export const GRID_DRAGEND = 'dragend';

// GRID events
Expand Down Expand Up @@ -42,7 +42,8 @@ export const GRID_SELECTION_CHANGED = 'selectionChange';
export const GRID_PAGE_CHANGED = 'pageChange';
export const GRID_PAGESIZE_CHANGED = 'pageSizeChange';

export const GRID_SCROLLING = 'scrolling';
export const GRID_ROWS_SCROLL = 'scrolling:rows';
export const GRID_ROWS_SCROLL_END = 'scroll:rowEnd';

export const GRID_COL_RESIZE_START = 'colResizing:start';
export const GRID_COL_RESIZE_STOP = 'colResizing:stop';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useGridInfiniteLoader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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 { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector';
import { useGridApiEventHandler } from '../../root/useGridApiEventHandler';
import { GridRowScrollEndParams } from '../../../models/params/gridRowScrollEndParams';
import { visibleGridColumnsSelector } from '../columns/gridColumnsSelector';

export const useGridInfiniteLoader = (apiRef: GridApiRef): void => {
const options = useGridSelector(apiRef, optionsSelector);
const containerSizes = useGridSelector(apiRef, gridContainerSizesSelector);
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector);
const isInScrollBottomArea = React.useRef<boolean>(false);

const handleGridScroll = React.useCallback(() => {
if (!containerSizes) {
return;
}

const scrollPosition = apiRef.current.getScrollPosition();
const scrollPositionBottom =
scrollPosition.top + containerSizes.windowSizes.height + options.scrollEndThreshold;

if (scrollPositionBottom < containerSizes.dataContainerSizes.height) {
isInScrollBottomArea.current = false;
}

if (
scrollPositionBottom >= containerSizes.dataContainerSizes.height &&
!isInScrollBottomArea.current
) {
const rowScrollEndParam: GridRowScrollEndParams = {
api: apiRef,
visibleColumns,
viewportPageSize: containerSizes.viewportPageSize,
virtualRowsCount: containerSizes.virtualRowsCount,
};
apiRef.current.publishEvent(GRID_ROWS_SCROLL_END, rowScrollEndParam);
isInScrollBottomArea.current = true;
}
}, [options, containerSizes, apiRef, visibleColumns]);

useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handleGridScroll);
useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL_END, options.onRowsScrollEnd);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { gridContainerSizesSelector } from '../../../components/GridViewport';
import { GRID_CELL_CSS_CLASS, GRID_ROW_CSS_CLASS } from '../../../constants/cssClassesConstants';
import {
GRID_ELEMENT_FOCUS_OUT,
Expand Down Expand Up @@ -33,6 +32,7 @@ import { useLogger } from '../../utils/useLogger';
import { useGridApiEventHandler } from '../../root/useGridApiEventHandler';
import { gridSelectionStateSelector } from '../selection/gridSelectionSelector';
import { KeyboardState } from './keyboardState';
import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector';

const getNextCellIndexes = (code: string, indexes: GridCellIndexCoordinates) => {
if (!isArrowKeys(code)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as React from 'react';
import { gridContainerSizesSelector } from '../../../components/GridViewport';
import { GRID_PAGE_CHANGED, GRID_PAGESIZE_CHANGED } from '../../../constants/eventsConstants';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { GridPaginationApi } from '../../../models/api/gridPaginationApi';
import { GridPageChangeParams } from '../../../models/params/gridPageChangeParams';
import { useGridApiEventHandler } from '../../root/useGridApiEventHandler';
import { useGridApiMethod } from '../../root/useGridApiMethod';
import { optionsSelector } from '../../utils/optionsSelector';
import { gridContainerSizesSelector } from '../../root/gridContainerSizesSelector';
import { useLogger } from '../../utils/useLogger';
import { useGridReducer } from '../core/useGridReducer';
import { useGridSelector } from '../core/useGridSelector';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { createSelector } from 'reselect';
import { GridScrollParams } from '../../../models/params/gridScrollParams';
import { GridState } from '../core/gridState';
import { InternalRenderingState } from './renderingState';

export const renderStateSelector = (state: GridState) => state.rendering;
export const scrollStateSelector = createSelector<
GridState,
InternalRenderingState,
GridScrollParams
>(renderStateSelector, (renderingState) => renderingState.realScroll);
Loading