Skip to content

Commit 30907ad

Browse files
MBilalShafiDungTiger
authored andcommitted
[DataGrid] Support advanced server-side pagination use cases (mui#12474)
1 parent 96c4720 commit 30907ad

52 files changed

Lines changed: 1145 additions & 181 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/data/data-grid/events/events.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,13 @@
254254
"event": "MuiEvent<{}>",
255255
"componentProp": "onMenuOpen"
256256
},
257+
{
258+
"projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"],
259+
"name": "paginationMetaChange",
260+
"description": "Fired when the pagination meta change.",
261+
"params": "GridPaginationMeta",
262+
"event": "MuiEvent<{}>"
263+
},
257264
{
258265
"projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"],
259266
"name": "paginationModelChange",

docs/data/data-grid/localization/localization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ One example is the table pagination component used in the Data Grid footer when
2424
localeText={{
2525
MuiTablePagination: {
2626
labelDisplayedRows: ({ from, to, count }) =>
27-
`${from} - ${to} of more than ${count}`,
27+
`${from} - ${to} of ${count === -1 ? `more than ${to}` : count}`,
2828
},
2929
}}
3030
/>
Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import * as React from 'react';
22
import { DataGrid } from '@mui/x-data-grid';
3+
import Radio from '@mui/material/Radio';
4+
import RadioGroup from '@mui/material/RadioGroup';
5+
import FormControlLabel from '@mui/material/FormControlLabel';
6+
import FormControl from '@mui/material/FormControl';
7+
import FormLabel from '@mui/material/FormLabel';
38
import { createFakeServer } from '@mui/x-data-grid-generator';
49

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

1318
export default function CursorPaginationGrid() {
19+
const [rowCountType, setRowCountType] = React.useState('known');
20+
1421
const mapPageToNextCursor = React.useRef({});
1522

1623
const [paginationModel, setPaginationModel] = React.useState({
@@ -25,7 +32,11 @@ export default function CursorPaginationGrid() {
2532
}),
2633
[paginationModel],
2734
);
28-
const { isLoading, rows, pageInfo } = useQuery(queryOptions);
35+
const {
36+
isLoading,
37+
rows,
38+
pageInfo: { hasNextPage, nextCursor, totalRowCount },
39+
} = useQuery(queryOptions);
2940

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

51+
const paginationMetaRef = React.useRef();
52+
53+
// Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch
54+
const paginationMeta = React.useMemo(() => {
55+
if (
56+
hasNextPage !== undefined &&
57+
paginationMetaRef.current?.hasNextPage !== hasNextPage
58+
) {
59+
paginationMetaRef.current = { hasNextPage };
60+
}
61+
return paginationMetaRef.current;
62+
}, [hasNextPage]);
63+
4064
React.useEffect(() => {
41-
if (!isLoading && pageInfo?.nextCursor) {
65+
if (!isLoading && nextCursor) {
4266
// We add nextCursor when available
43-
mapPageToNextCursor.current[paginationModel.page] = pageInfo?.nextCursor;
67+
mapPageToNextCursor.current[paginationModel.page] = nextCursor;
4468
}
45-
}, [paginationModel.page, isLoading, pageInfo?.nextCursor]);
69+
}, [paginationModel.page, isLoading, nextCursor]);
4670

4771
// Some API clients return undefined while loading
4872
// Following lines are here to prevent `rowCountState` from being undefined during the loading
49-
const [rowCountState, setRowCountState] = React.useState(
50-
pageInfo?.totalRowCount || 0,
51-
);
73+
const [rowCountState, setRowCountState] = React.useState(totalRowCount || 0);
5274
React.useEffect(() => {
53-
setRowCountState((prevRowCountState) =>
54-
pageInfo?.totalRowCount !== undefined
55-
? pageInfo?.totalRowCount
56-
: prevRowCountState,
57-
);
58-
}, [pageInfo?.totalRowCount, setRowCountState]);
75+
if (rowCountType === 'known') {
76+
setRowCountState((prevRowCountState) =>
77+
totalRowCount !== undefined ? totalRowCount : prevRowCountState,
78+
);
79+
}
80+
if (
81+
(rowCountType === 'unknown' || rowCountType === 'estimated') &&
82+
paginationMeta?.hasNextPage !== false
83+
) {
84+
setRowCountState(-1);
85+
}
86+
}, [paginationMeta?.hasNextPage, rowCountType, totalRowCount]);
87+
88+
const prevEstimatedRowCount = React.useRef(undefined);
89+
const estimatedRowCount = React.useMemo(() => {
90+
if (rowCountType === 'estimated') {
91+
if (totalRowCount !== undefined) {
92+
if (prevEstimatedRowCount.current === undefined) {
93+
prevEstimatedRowCount.current = totalRowCount / 2;
94+
}
95+
return totalRowCount / 2;
96+
}
97+
return prevEstimatedRowCount.current;
98+
}
99+
return undefined;
100+
}, [rowCountType, totalRowCount]);
59101

60102
return (
61-
<div style={{ height: 400, width: '100%' }}>
62-
<DataGrid
63-
rows={rows}
64-
{...data}
65-
pageSizeOptions={[PAGE_SIZE]}
66-
rowCount={rowCountState}
67-
paginationMode="server"
68-
onPaginationModelChange={handlePaginationModelChange}
69-
paginationModel={paginationModel}
70-
loading={isLoading}
71-
/>
103+
<div style={{ width: '100%' }}>
104+
<FormControl>
105+
<FormLabel id="demo-cursor-pagination-buttons-group-label">
106+
Row count
107+
</FormLabel>
108+
<RadioGroup
109+
row
110+
aria-labelledby="demo-cursor-pagination-buttons-group-label"
111+
name="cursor-pagination-buttons-group"
112+
value={rowCountType}
113+
onChange={(e) => setRowCountType(e.target.value)}
114+
>
115+
<FormControlLabel value="known" control={<Radio />} label="Known" />
116+
<FormControlLabel value="unknown" control={<Radio />} label="Unknown" />
117+
<FormControlLabel
118+
value="estimated"
119+
control={<Radio />}
120+
label="Estimated"
121+
/>
122+
</RadioGroup>
123+
</FormControl>
124+
<div style={{ height: 400 }}>
125+
<DataGrid
126+
rows={rows}
127+
{...data}
128+
pageSizeOptions={[PAGE_SIZE]}
129+
rowCount={rowCountState}
130+
onRowCountChange={(newRowCount) => setRowCountState(newRowCount)}
131+
estimatedRowCount={estimatedRowCount}
132+
paginationMeta={paginationMeta}
133+
paginationMode="server"
134+
onPaginationModelChange={handlePaginationModelChange}
135+
paginationModel={paginationModel}
136+
loading={isLoading}
137+
/>
138+
</div>
72139
</div>
73140
);
74141
}
Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import * as React from 'react';
2-
import { DataGrid, GridRowId, GridPaginationModel } from '@mui/x-data-grid';
2+
import {
3+
DataGrid,
4+
GridRowId,
5+
GridPaginationModel,
6+
GridPaginationMeta,
7+
} from '@mui/x-data-grid';
8+
import Radio from '@mui/material/Radio';
9+
import RadioGroup from '@mui/material/RadioGroup';
10+
import FormControlLabel from '@mui/material/FormControlLabel';
11+
import FormControl from '@mui/material/FormControl';
12+
import FormLabel from '@mui/material/FormLabel';
313
import { createFakeServer } from '@mui/x-data-grid-generator';
414

515
const PAGE_SIZE = 5;
@@ -10,7 +20,11 @@ const SERVER_OPTIONS = {
1020

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

23+
type RowCountType = 'known' | 'unknown' | 'estimated';
24+
1325
export default function CursorPaginationGrid() {
26+
const [rowCountType, setRowCountType] = React.useState<RowCountType>('known');
27+
1428
const mapPageToNextCursor = React.useRef<{ [page: number]: GridRowId }>({});
1529

1630
const [paginationModel, setPaginationModel] = React.useState({
@@ -25,7 +39,11 @@ export default function CursorPaginationGrid() {
2539
}),
2640
[paginationModel],
2741
);
28-
const { isLoading, rows, pageInfo } = useQuery(queryOptions);
42+
const {
43+
isLoading,
44+
rows,
45+
pageInfo: { hasNextPage, nextCursor, totalRowCount },
46+
} = useQuery(queryOptions);
2947

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

58+
const paginationMetaRef = React.useRef<GridPaginationMeta>();
59+
60+
// Memoize to avoid flickering when the `hasNextPage` is `undefined` during refetch
61+
const paginationMeta = React.useMemo(() => {
62+
if (
63+
hasNextPage !== undefined &&
64+
paginationMetaRef.current?.hasNextPage !== hasNextPage
65+
) {
66+
paginationMetaRef.current = { hasNextPage };
67+
}
68+
return paginationMetaRef.current;
69+
}, [hasNextPage]);
70+
4071
React.useEffect(() => {
41-
if (!isLoading && pageInfo?.nextCursor) {
72+
if (!isLoading && nextCursor) {
4273
// We add nextCursor when available
43-
mapPageToNextCursor.current[paginationModel.page] = pageInfo?.nextCursor;
74+
mapPageToNextCursor.current[paginationModel.page] = nextCursor;
4475
}
45-
}, [paginationModel.page, isLoading, pageInfo?.nextCursor]);
76+
}, [paginationModel.page, isLoading, nextCursor]);
4677

4778
// Some API clients return undefined while loading
4879
// Following lines are here to prevent `rowCountState` from being undefined during the loading
49-
const [rowCountState, setRowCountState] = React.useState(
50-
pageInfo?.totalRowCount || 0,
51-
);
80+
const [rowCountState, setRowCountState] = React.useState(totalRowCount || 0);
5281
React.useEffect(() => {
53-
setRowCountState((prevRowCountState) =>
54-
pageInfo?.totalRowCount !== undefined
55-
? pageInfo?.totalRowCount
56-
: prevRowCountState,
57-
);
58-
}, [pageInfo?.totalRowCount, setRowCountState]);
82+
if (rowCountType === 'known') {
83+
setRowCountState((prevRowCountState) =>
84+
totalRowCount !== undefined ? totalRowCount : prevRowCountState,
85+
);
86+
}
87+
if (
88+
(rowCountType === 'unknown' || rowCountType === 'estimated') &&
89+
paginationMeta?.hasNextPage !== false
90+
) {
91+
setRowCountState(-1);
92+
}
93+
}, [paginationMeta?.hasNextPage, rowCountType, totalRowCount]);
94+
95+
const prevEstimatedRowCount = React.useRef<number | undefined>(undefined);
96+
const estimatedRowCount = React.useMemo(() => {
97+
if (rowCountType === 'estimated') {
98+
if (totalRowCount !== undefined) {
99+
if (prevEstimatedRowCount.current === undefined) {
100+
prevEstimatedRowCount.current = totalRowCount / 2;
101+
}
102+
return totalRowCount / 2;
103+
}
104+
return prevEstimatedRowCount.current;
105+
}
106+
return undefined;
107+
}, [rowCountType, totalRowCount]);
59108

60109
return (
61-
<div style={{ height: 400, width: '100%' }}>
62-
<DataGrid
63-
rows={rows}
64-
{...data}
65-
pageSizeOptions={[PAGE_SIZE]}
66-
rowCount={rowCountState}
67-
paginationMode="server"
68-
onPaginationModelChange={handlePaginationModelChange}
69-
paginationModel={paginationModel}
70-
loading={isLoading}
71-
/>
110+
<div style={{ width: '100%' }}>
111+
<FormControl>
112+
<FormLabel id="demo-cursor-pagination-buttons-group-label">
113+
Row count
114+
</FormLabel>
115+
<RadioGroup
116+
row
117+
aria-labelledby="demo-cursor-pagination-buttons-group-label"
118+
name="cursor-pagination-buttons-group"
119+
value={rowCountType}
120+
onChange={(e) => setRowCountType(e.target.value as RowCountType)}
121+
>
122+
<FormControlLabel value="known" control={<Radio />} label="Known" />
123+
<FormControlLabel value="unknown" control={<Radio />} label="Unknown" />
124+
<FormControlLabel
125+
value="estimated"
126+
control={<Radio />}
127+
label="Estimated"
128+
/>
129+
</RadioGroup>
130+
</FormControl>
131+
<div style={{ height: 400 }}>
132+
<DataGrid
133+
rows={rows}
134+
{...data}
135+
pageSizeOptions={[PAGE_SIZE]}
136+
rowCount={rowCountState}
137+
onRowCountChange={(newRowCount) => setRowCountState(newRowCount)}
138+
estimatedRowCount={estimatedRowCount}
139+
paginationMeta={paginationMeta}
140+
paginationMode="server"
141+
onPaginationModelChange={handlePaginationModelChange}
142+
paginationModel={paginationModel}
143+
loading={isLoading}
144+
/>
145+
</div>
72146
</div>
73147
);
74148
}

docs/data/data-grid/pagination/CursorPaginationGrid.tsx.preview

Lines changed: 0 additions & 10 deletions
This file was deleted.

docs/data/data-grid/pagination/ServerPaginationGrid.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,22 @@ export default function ServerPaginationGrid() {
1717
const { isLoading, rows, pageInfo } = useQuery(paginationModel);
1818

1919
// Some API clients return undefined while loading
20-
// Following lines are here to prevent `rowCountState` from being undefined during the loading
21-
const [rowCountState, setRowCountState] = React.useState(
22-
pageInfo?.totalRowCount || 0,
23-
);
24-
React.useEffect(() => {
25-
setRowCountState((prevRowCountState) =>
26-
pageInfo?.totalRowCount !== undefined
27-
? pageInfo?.totalRowCount
28-
: prevRowCountState,
29-
);
30-
}, [pageInfo?.totalRowCount, setRowCountState]);
20+
// Following lines are here to prevent `rowCount` from being undefined during the loading
21+
const rowCountRef = React.useRef(pageInfo?.totalRowCount || 0);
22+
23+
const rowCount = React.useMemo(() => {
24+
if (pageInfo?.totalRowCount !== undefined) {
25+
rowCountRef.current = pageInfo.totalRowCount;
26+
}
27+
return rowCountRef.current;
28+
}, [pageInfo?.totalRowCount]);
3129

3230
return (
3331
<div style={{ height: 400, width: '100%' }}>
3432
<DataGrid
3533
rows={rows}
3634
{...data}
37-
rowCount={rowCountState}
35+
rowCount={rowCount}
3836
loading={isLoading}
3937
pageSizeOptions={[5]}
4038
paginationModel={paginationModel}

0 commit comments

Comments
 (0)