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] Support advanced server-side pagination use cases #12474

Merged
merged 32 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4aa33fd
[DataGrid] Support advanced server side pagination use cases
MBilalShafi Mar 18, 2024
09ec00c
Wrap labelDisplayedRows
MBilalShafi Mar 18, 2024
2bdba17
Rebuild docs
MBilalShafi Mar 18, 2024
6df8b15
Housekeeping
MBilalShafi Mar 18, 2024
10423db
Fix test
MBilalShafi Mar 18, 2024
1e099fe
CI
MBilalShafi Mar 18, 2024
e81f588
Merge latest master
MBilalShafi Mar 21, 2024
595d79f
Improve docs a bit
MBilalShafi Mar 21, 2024
07365d9
Improvement
MBilalShafi Mar 21, 2024
2660ce9
Event update
MBilalShafi Mar 21, 2024
a9254ec
Add tests
MBilalShafi Mar 21, 2024
67b4f4b
Lint
MBilalShafi Mar 21, 2024
58741d7
Merge master into row-count-unknown-estimated
MBilalShafi Apr 2, 2024
157bc46
docs:api
MBilalShafi Apr 2, 2024
27e38d1
Allow localization for estimated use-case + docs improvement
MBilalShafi Apr 2, 2024
1309497
test_types fix
MBilalShafi Apr 2, 2024
c268a42
Merge 'master' into row-count-unknown-estimated
MBilalShafi Apr 4, 2024
18ae445
Improvement
MBilalShafi Apr 5, 2024
4f8f45d
Accommodate inconsistent last page row length
MBilalShafi Apr 5, 2024
2dfd0e1
Refactors
MBilalShafi Apr 5, 2024
ec4340b
Simplify the effects
MBilalShafi Apr 5, 2024
ac66714
lint
MBilalShafi Apr 5, 2024
cbe16b0
Lint + docs minor update
MBilalShafi Apr 5, 2024
6f698ac
Merge branch 'master' into row-count-unknown-estimated
MBilalShafi Apr 11, 2024
a234af2
Update demos
MBilalShafi Apr 11, 2024
e1addb5
Remove redundant condition
MBilalShafi Apr 11, 2024
cc95127
Merge branch 'master' into row-count-unknown-estimated
MBilalShafi Apr 15, 2024
253791c
Apply suggestions from code review
MBilalShafi Apr 15, 2024
26fbd67
Update docs
MBilalShafi Apr 15, 2024
0a0ef75
A few updates here and there
MBilalShafi Apr 17, 2024
ad26ba1
Apply suggestions from code review
MBilalShafi Apr 17, 2024
2f8f601
Update the docs
MBilalShafi Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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(() => {
cherniavskii marked this conversation as resolved.
Show resolved Hide resolved
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>
);
}
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