Skip to content

Commit

Permalink
[DataGrid] Support advanced server-side pagination use cases (#12474)
Browse files Browse the repository at this point in the history
  • Loading branch information
MBilalShafi authored Apr 18, 2024
1 parent de60920 commit 70faafc
Show file tree
Hide file tree
Showing 52 changed files with 1,145 additions and 181 deletions.
7 changes: 7 additions & 0 deletions docs/data/data-grid/events/events.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@
"event": "MuiEvent<{}>",
"componentProp": "onMenuOpen"
},
{
"projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"],
"name": "paginationMetaChange",
"description": "Fired when the pagination meta change.",
"params": "GridPaginationMeta",
"event": "MuiEvent<{}>"
},
{
"projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"],
"name": "paginationModelChange",
Expand Down
2 changes: 1 addition & 1 deletion docs/data/data-grid/localization/localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ One example is the table pagination component used in the Data Grid footer when
localeText={{
MuiTablePagination: {
labelDisplayedRows: ({ from, to, count }) =>
`${from} - ${to} of more than ${count}`,
`${from} - ${to} of ${count === -1 ? `more than ${to}` : count}`,
},
}}
/>
Expand Down
115 changes: 91 additions & 24 deletions docs/data/data-grid/pagination/CursorPaginationGrid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import * as React from 'react';
import { DataGrid } from '@mui/x-data-grid';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import { createFakeServer } from '@mui/x-data-grid-generator';

const PAGE_SIZE = 5;
Expand All @@ -11,6 +16,8 @@ const SERVER_OPTIONS = {
const { useQuery, ...data } = createFakeServer({}, SERVER_OPTIONS);

export default function CursorPaginationGrid() {
const [rowCountType, setRowCountType] = React.useState('known');

const mapPageToNextCursor = React.useRef({});

const [paginationModel, setPaginationModel] = React.useState({
Expand All @@ -25,7 +32,11 @@ export default function CursorPaginationGrid() {
}),
[paginationModel],
);
const { isLoading, rows, pageInfo } = useQuery(queryOptions);
const {
isLoading,
rows,
pageInfo: { hasNextPage, nextCursor, totalRowCount },
} = useQuery(queryOptions);

const handlePaginationModelChange = (newPaginationModel) => {
// We have the cursor, we can allow the page transition.
Expand All @@ -37,38 +48,94 @@ export default function CursorPaginationGrid() {
}
};

const paginationMetaRef = React.useRef();

// Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch
const paginationMeta = React.useMemo(() => {
if (
hasNextPage !== undefined &&
paginationMetaRef.current?.hasNextPage !== hasNextPage
) {
paginationMetaRef.current = { hasNextPage };
}
return paginationMetaRef.current;
}, [hasNextPage]);

React.useEffect(() => {
if (!isLoading && pageInfo?.nextCursor) {
if (!isLoading && nextCursor) {
// We add nextCursor when available
mapPageToNextCursor.current[paginationModel.page] = pageInfo?.nextCursor;
mapPageToNextCursor.current[paginationModel.page] = nextCursor;
}
}, [paginationModel.page, isLoading, pageInfo?.nextCursor]);
}, [paginationModel.page, isLoading, nextCursor]);

// Some API clients return undefined while loading
// Following lines are here to prevent `rowCountState` from being undefined during the loading
const [rowCountState, setRowCountState] = React.useState(
pageInfo?.totalRowCount || 0,
);
const [rowCountState, setRowCountState] = React.useState(totalRowCount || 0);
React.useEffect(() => {
setRowCountState((prevRowCountState) =>
pageInfo?.totalRowCount !== undefined
? pageInfo?.totalRowCount
: prevRowCountState,
);
}, [pageInfo?.totalRowCount, setRowCountState]);
if (rowCountType === 'known') {
setRowCountState((prevRowCountState) =>
totalRowCount !== undefined ? totalRowCount : prevRowCountState,
);
}
if (
(rowCountType === 'unknown' || rowCountType === 'estimated') &&
paginationMeta?.hasNextPage !== false
) {
setRowCountState(-1);
}
}, [paginationMeta?.hasNextPage, rowCountType, totalRowCount]);

const prevEstimatedRowCount = React.useRef(undefined);
const estimatedRowCount = React.useMemo(() => {
if (rowCountType === 'estimated') {
if (totalRowCount !== undefined) {
if (prevEstimatedRowCount.current === undefined) {
prevEstimatedRowCount.current = totalRowCount / 2;
}
return totalRowCount / 2;
}
return prevEstimatedRowCount.current;
}
return undefined;
}, [rowCountType, totalRowCount]);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
{...data}
pageSizeOptions={[PAGE_SIZE]}
rowCount={rowCountState}
paginationMode="server"
onPaginationModelChange={handlePaginationModelChange}
paginationModel={paginationModel}
loading={isLoading}
/>
<div style={{ width: '100%' }}>
<FormControl>
<FormLabel id="demo-cursor-pagination-buttons-group-label">
Row count
</FormLabel>
<RadioGroup
row
aria-labelledby="demo-cursor-pagination-buttons-group-label"
name="cursor-pagination-buttons-group"
value={rowCountType}
onChange={(e) => setRowCountType(e.target.value)}
>
<FormControlLabel value="known" control={<Radio />} label="Known" />
<FormControlLabel value="unknown" control={<Radio />} label="Unknown" />
<FormControlLabel
value="estimated"
control={<Radio />}
label="Estimated"
/>
</RadioGroup>
</FormControl>
<div style={{ height: 400 }}>
<DataGrid
rows={rows}
{...data}
pageSizeOptions={[PAGE_SIZE]}
rowCount={rowCountState}
onRowCountChange={(newRowCount) => setRowCountState(newRowCount)}
estimatedRowCount={estimatedRowCount}
paginationMeta={paginationMeta}
paginationMode="server"
onPaginationModelChange={handlePaginationModelChange}
paginationModel={paginationModel}
loading={isLoading}
/>
</div>
</div>
);
}
124 changes: 99 additions & 25 deletions docs/data/data-grid/pagination/CursorPaginationGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import * as React from 'react';
import { DataGrid, GridRowId, GridPaginationModel } from '@mui/x-data-grid';
import {
DataGrid,
GridRowId,
GridPaginationModel,
GridPaginationMeta,
} from '@mui/x-data-grid';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel';
import { createFakeServer } from '@mui/x-data-grid-generator';

const PAGE_SIZE = 5;
Expand All @@ -10,7 +20,11 @@ const SERVER_OPTIONS = {

const { useQuery, ...data } = createFakeServer({}, SERVER_OPTIONS);

type RowCountType = 'known' | 'unknown' | 'estimated';

export default function CursorPaginationGrid() {
const [rowCountType, setRowCountType] = React.useState<RowCountType>('known');

const mapPageToNextCursor = React.useRef<{ [page: number]: GridRowId }>({});

const [paginationModel, setPaginationModel] = React.useState({
Expand All @@ -25,7 +39,11 @@ export default function CursorPaginationGrid() {
}),
[paginationModel],
);
const { isLoading, rows, pageInfo } = useQuery(queryOptions);
const {
isLoading,
rows,
pageInfo: { hasNextPage, nextCursor, totalRowCount },
} = useQuery(queryOptions);

const handlePaginationModelChange = (newPaginationModel: GridPaginationModel) => {
// We have the cursor, we can allow the page transition.
Expand All @@ -37,38 +55,94 @@ export default function CursorPaginationGrid() {
}
};

const paginationMetaRef = React.useRef<GridPaginationMeta>();

// Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch
const paginationMeta = React.useMemo(() => {
if (
hasNextPage !== undefined &&
paginationMetaRef.current?.hasNextPage !== hasNextPage
) {
paginationMetaRef.current = { hasNextPage };
}
return paginationMetaRef.current;
}, [hasNextPage]);

React.useEffect(() => {
if (!isLoading && pageInfo?.nextCursor) {
if (!isLoading && nextCursor) {
// We add nextCursor when available
mapPageToNextCursor.current[paginationModel.page] = pageInfo?.nextCursor;
mapPageToNextCursor.current[paginationModel.page] = nextCursor;
}
}, [paginationModel.page, isLoading, pageInfo?.nextCursor]);
}, [paginationModel.page, isLoading, nextCursor]);

// Some API clients return undefined while loading
// Following lines are here to prevent `rowCountState` from being undefined during the loading
const [rowCountState, setRowCountState] = React.useState(
pageInfo?.totalRowCount || 0,
);
const [rowCountState, setRowCountState] = React.useState(totalRowCount || 0);
React.useEffect(() => {
setRowCountState((prevRowCountState) =>
pageInfo?.totalRowCount !== undefined
? pageInfo?.totalRowCount
: prevRowCountState,
);
}, [pageInfo?.totalRowCount, setRowCountState]);
if (rowCountType === 'known') {
setRowCountState((prevRowCountState) =>
totalRowCount !== undefined ? totalRowCount : prevRowCountState,
);
}
if (
(rowCountType === 'unknown' || rowCountType === 'estimated') &&
paginationMeta?.hasNextPage !== false
) {
setRowCountState(-1);
}
}, [paginationMeta?.hasNextPage, rowCountType, totalRowCount]);

const prevEstimatedRowCount = React.useRef<number | undefined>(undefined);
const estimatedRowCount = React.useMemo(() => {
if (rowCountType === 'estimated') {
if (totalRowCount !== undefined) {
if (prevEstimatedRowCount.current === undefined) {
prevEstimatedRowCount.current = totalRowCount / 2;
}
return totalRowCount / 2;
}
return prevEstimatedRowCount.current;
}
return undefined;
}, [rowCountType, totalRowCount]);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
{...data}
pageSizeOptions={[PAGE_SIZE]}
rowCount={rowCountState}
paginationMode="server"
onPaginationModelChange={handlePaginationModelChange}
paginationModel={paginationModel}
loading={isLoading}
/>
<div style={{ width: '100%' }}>
<FormControl>
<FormLabel id="demo-cursor-pagination-buttons-group-label">
Row count
</FormLabel>
<RadioGroup
row
aria-labelledby="demo-cursor-pagination-buttons-group-label"
name="cursor-pagination-buttons-group"
value={rowCountType}
onChange={(e) => setRowCountType(e.target.value as RowCountType)}
>
<FormControlLabel value="known" control={<Radio />} label="Known" />
<FormControlLabel value="unknown" control={<Radio />} label="Unknown" />
<FormControlLabel
value="estimated"
control={<Radio />}
label="Estimated"
/>
</RadioGroup>
</FormControl>
<div style={{ height: 400 }}>
<DataGrid
rows={rows}
{...data}
pageSizeOptions={[PAGE_SIZE]}
rowCount={rowCountState}
onRowCountChange={(newRowCount) => setRowCountState(newRowCount)}
estimatedRowCount={estimatedRowCount}
paginationMeta={paginationMeta}
paginationMode="server"
onPaginationModelChange={handlePaginationModelChange}
paginationModel={paginationModel}
loading={isLoading}
/>
</div>
</div>
);
}
10 changes: 0 additions & 10 deletions docs/data/data-grid/pagination/CursorPaginationGrid.tsx.preview

This file was deleted.

22 changes: 10 additions & 12 deletions docs/data/data-grid/pagination/ServerPaginationGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,22 @@ export default function ServerPaginationGrid() {
const { isLoading, rows, pageInfo } = useQuery(paginationModel);

// Some API clients return undefined while loading
// Following lines are here to prevent `rowCountState` from being undefined during the loading
const [rowCountState, setRowCountState] = React.useState(
pageInfo?.totalRowCount || 0,
);
React.useEffect(() => {
setRowCountState((prevRowCountState) =>
pageInfo?.totalRowCount !== undefined
? pageInfo?.totalRowCount
: prevRowCountState,
);
}, [pageInfo?.totalRowCount, setRowCountState]);
// Following lines are here to prevent `rowCount` from being undefined during the loading
const rowCountRef = React.useRef(pageInfo?.totalRowCount || 0);

const rowCount = React.useMemo(() => {
if (pageInfo?.totalRowCount !== undefined) {
rowCountRef.current = pageInfo.totalRowCount;
}
return rowCountRef.current;
}, [pageInfo?.totalRowCount]);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
{...data}
rowCount={rowCountState}
rowCount={rowCount}
loading={isLoading}
pageSizeOptions={[5]}
paginationModel={paginationModel}
Expand Down
Loading

0 comments on commit 70faafc

Please sign in to comment.