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 9 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 packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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 @@ -90,6 +91,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
useGridColumnResize(columnsHeaderRef, apiRef);
useGridPagination(apiRef);
useGridCsvExport(apiRef);
useGridInfiniteLoader(windowRef, apiRef);
Copy link
Member

Choose a reason for hiding this comment

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

Not sure about loader
useGridInfiniteLoader > useGridInfiniteScroll?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is tricky with the name. Both seem to mean similar things. I chose the loader as that was how it was referred to in the requested feature.


const components = useGridComponents(props.components, props.componentsProps, apiRef);
useStateProp(apiRef, props.state);
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/utils/sizesSelector';
DanailH marked this conversation as resolved.
Show resolved Hide resolved

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 { GridLeftEmptyCell, GridRightEmptyCell } from '../GridCell';
import { GridScrollArea } from '../GridScrollArea';
import { gridContainerSizesSelector } from '../GridViewport';
import { GridColumnHeadersItemCollection } from './GridColumnHeadersItemCollection';
import { gridDensityHeaderHeightSelector } from '../../hooks/features/density/densitySelector';
import { gridContainerSizesSelector } from '../../hooks/utils/sizesSelector';

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

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,35 @@
import * as React from 'react';
import { optionsSelector } from '../../utils/optionsSelector';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { useGridSelector } from '../core/useGridSelector';
import { useNativeEventListener } from '../../root/useNativeEventListener';
import { GRID_SCROLL } from '../../../constants/eventsConstants';
import { gridContainerSizesSelector } from '../../utils/sizesSelector';

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

const handleGridScroll = React.useCallback(
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
(event) => {
const scrollPositionBottom =
event.target.scrollTop + containerSizes?.windowSizes.height + options.scrollBottomThreshold;

if (containerSizes && scrollPositionBottom >= containerSizes.dataContainerSizes.height) {
if (!isInScrollBottomArea.current && options.onScrollBottom) {
isInScrollBottomArea.current = true;
options.onScrollBottom();
DanailH marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
DanailH marked this conversation as resolved.
Show resolved Hide resolved
isInScrollBottomArea.current = false;
}
},
[options, containerSizes],
);

useNativeEventListener(apiRef, windowRef, GRID_SCROLL, handleGridScroll, { passive: true });
Copy link
Member

Choose a reason for hiding this comment

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

there is already a scroll event in the grid, why not use it?

  useGridApiEventHandler(api as GridApiRef, GRID_SCROLLING, handleScrolling);

This is also a state.isScrolling that let you know when the grid is scrolling. We could add the position...

Copy link
Member

@oliviertassinari oliviertassinari Mar 10, 2021

Choose a reason for hiding this comment

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

I don't get it. Why should we care about the "scrolling" state (a boolean?)? Isn't the "scroll position" state that we are interested into? Maybe there is a subtlety between the two that I don't get. I thought that "scrolling" state was used to set pointer-event: none on the viewport, like it's a boolean state.

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, I can explain. So, yes @dtassone there is a scrolling event on the grid that returns the top and left positions of the scroll. The problem is that when the virtualization kicks in those values are being reset back to 0 so it isn't possible to know the actual scroll top for the entire grid viewport. That's why I'm using this one. it works the same way and it is not fired any more times than the other but it returns the correct position.

Regarding the isScrolling I also don't see a benefit of adding it.

Copy link
Member

Choose a reason for hiding this comment

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

I see the issue, that's because it's the position of the rendering zone. Maybe we should modify it to add the window scroll position so you could hook into this one for the case we are covering in this PR and then we already have the base for the case of infinite scroll server side hooked into virtualisation...

Regarding the isScrolling I also don't see a benefit of adding it.

It's just an approach that use the state instead of the event... Probably better perf with event on this one.

Copy link
Member

@dtassone dtassone Mar 10, 2021

Choose a reason for hiding this comment

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

the main reason for not attaching the event to window is to decouple the rendering from the feature.
So if I refactor the rendering engine and remove the window then I will have to refactor this hook as well.
By using the existing scrolling event, you allow to decouple the virtualisation of this feature. So if I refactor the virtualisation and render just one div with absolute position for example, then I will just need to keep firing the event with the right contract to make sure this feature still works.

Copy link
Member

Choose a reason for hiding this comment

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

same PR is fine as it's a very close concern

Copy link
Member

Choose a reason for hiding this comment

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

not resolved

Copy link
Member

@oliviertassinari oliviertassinari Mar 12, 2021

Choose a reason for hiding this comment

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

We don't need the change to ship value to the developers with this new callback. I would personally encourage doing it afterward IF the diff is significant (e.g. +/-100 LOCs would strongly signal that it's too large for a side improvement).

Copy link
Member Author

@DanailH DanailH Mar 12, 2021

Choose a reason for hiding this comment

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

Ok, @dtassone how about I add a third prop here https://github.com/mui-org/material-ui-x/blob/master/packages/grid/_modules_/grid/hooks/features/virtualization/useGridVirtualRows.ts#L145 that is absoluteTop and just pass in the scrollTop position that has the correct value because changing the top breaks calculation for the scrollarea. That would allow me to use useGridApiEventHandler(apiRef, GRID_ROWS_SCROLL, handler); in the Infinite Loader hook?

PS: if that is what you meant in the first place then my appologies, I was thinking in the direction of reworking the scroll logic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated

};
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 '../../utils/sizesSelector';

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 '../../utils/sizesSelector';
import { useLogger } from '../../utils/useLogger';
import { useGridReducer } from '../core/useGridReducer';
import { useGridSelector } from '../core/useGridSelector';
Expand Down
5 changes: 5 additions & 0 deletions packages/grid/_modules_/grid/hooks/utils/sizesSelector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GridState } from '../features/core/gridState';

export const gridContainerSizesSelector = (state: GridState) => state.containerSizes;
export const gridViewportSizesSelector = (state: GridState) => state.viewportSizes;
export const gridScrollBarSizeSelector = (state: GridState) => state.scrollBar;
9 changes: 9 additions & 0 deletions packages/grid/_modules_/grid/models/gridOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,14 @@ export interface GridOptions {
* 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.
*/
localeText: Partial<GridLocaleText>;
/**
*
*/
onScrollBottom?: () => void;
DanailH marked this conversation as resolved.
Show resolved Hide resolved
/**
*
*/
scrollBottomThreshold: number;
}

/**
Expand All @@ -347,6 +355,7 @@ export const DEFAULT_GRID_OPTIONS: GridOptions = {
columnBuffer: 2,
rowsPerPageOptions: [25, 50, 100],
pageSize: 100,
scrollBottomThreshold: 80,
DanailH marked this conversation as resolved.
Show resolved Hide resolved
paginationMode: GridFeatureModeConstant.client,
sortingMode: GridFeatureModeConstant.client,
filterMode: GridFeatureModeConstant.client,
Expand Down
10 changes: 5 additions & 5 deletions packages/grid/x-grid-data-generator/src/useDemoData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const useDemoData = (options: DemoDataOptions): DemoDataReturnType => {
const [loading, setLoading] = React.useState(true);

React.useEffect(() => {
const cacheKey = `${options.dataSet}-${rowLength}-${index}-${options.maxColumns}`;
const cacheKey = `${options.dataSet}-${options.rowLength}-${index}-${options.maxColumns}`;
DanailH marked this conversation as resolved.
Show resolved Hide resolved

// Cache to allow fast switch between the JavaScript and TypeScript version
// of the demos.
Expand All @@ -107,11 +107,11 @@ export const useDemoData = (options: DemoDataOptions): DemoDataReturnType => {

let newData;

if (rowLength > 1000) {
if (options.rowLength > 1000) {
newData = await getRealData(1000, columns);
newData = await extrapolateSeed(rowLength, columns, newData);
newData = await extrapolateSeed(options.rowLength, columns, newData);
} else {
newData = await getRealData(rowLength, columns);
newData = await getRealData(options.rowLength, columns);
}

if (!active) {
Expand All @@ -131,7 +131,7 @@ export const useDemoData = (options: DemoDataOptions): DemoDataReturnType => {
return () => {
active = false;
};
}, [rowLength, options.dataSet, index, options.maxColumns]);
}, [options.rowLength, options.dataSet, index, options.maxColumns]);

return {
data,
Expand Down
42 changes: 41 additions & 1 deletion packages/storybook/src/stories/grid-rendering.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import * as React from 'react';
import { XGrid } from '@material-ui/x-grid';
import { XGrid, GridOverlay, useGridApiRef } from '@material-ui/x-grid';
import LinearProgress from '@material-ui/core/LinearProgress';
import { useDemoData } from '@material-ui/x-grid-data-generator';
import '../style/grid-stories.css';
import { action } from '@storybook/addon-actions';

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

export default {
title: 'X-Grid Tests/Rendering',
component: XGrid,
Expand Down Expand Up @@ -40,3 +52,31 @@ export const RenderInputInCell = () => {
</div>
);
};
export const InfiniteLoading = () => {
const apiRef = useGridApiRef();
const [size, setSize] = React.useState(50);
const { loading, data, loadNewData } = useDemoData({
dataSet: 'Commodity',
rowLength: size,
maxColumns: 20,
});

const handleOnScrollBottom = () => {
setSize(size + 20);
loadNewData();
};

return (
<div className="grid-container">
<XGrid
{...data}
apiRef={apiRef}
loading={loading}
onScrollBottom={handleOnScrollBottom}
components={{
LoadingOverlay: CustomLoadingOverlay,
}}
/>
</div>
);
};