diff --git a/docs/data/data-grid/filtering/FilteringWithPageReset.js b/docs/data/data-grid/filtering/FilteringWithPageReset.js new file mode 100644 index 0000000000000..fe1819b535bc1 --- /dev/null +++ b/docs/data/data-grid/filtering/FilteringWithPageReset.js @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +export default function FilteringWithPageReset() { + const { data, loading } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/FilteringWithPageReset.tsx b/docs/data/data-grid/filtering/FilteringWithPageReset.tsx new file mode 100644 index 0000000000000..fe1819b535bc1 --- /dev/null +++ b/docs/data/data-grid/filtering/FilteringWithPageReset.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import { DataGrid, GridToolbar } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +export default function FilteringWithPageReset() { + const { data, loading } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering/FilteringWithPageReset.tsx.preview b/docs/data/data-grid/filtering/FilteringWithPageReset.tsx.preview new file mode 100644 index 0000000000000..72c3e0f9e6081 --- /dev/null +++ b/docs/data/data-grid/filtering/FilteringWithPageReset.tsx.preview @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering/index.md b/docs/data/data-grid/filtering/index.md index 3a1eff48b1349..35cacd8dc164c 100644 --- a/docs/data/data-grid/filtering/index.md +++ b/docs/data/data-grid/filtering/index.md @@ -147,6 +147,14 @@ const columns = [ {{"demo": "ReadOnlyFilters.js", "bg": "inline", "defaultCodeOpen": false}} +## Reset page on filtering + +By default, the user stays on the same page after a filter is applied, unless the new row count indicates that that page doesn't exist anymore. +In that case, the user is sent to the last page as defined by the new row count. +To send the user back to the first page when a new filter is applied, use the `resetPageOnSortFilter` prop. + +{{"demo": "FilteringWithPageReset.js", "bg": "inline", "defaultCodeOpen": false}} + ## Ignore diacritics (accents) You can ignore diacritics (accents) when filtering the rows. See [Quick filter - Ignore diacritics (accents)](/x/react-data-grid/filtering/quick-filter/#ignore-diacritics-accents). diff --git a/docs/data/data-grid/filtering/server-side.md b/docs/data/data-grid/filtering/server-side.md index b579126d9049d..91e81bed48828 100644 --- a/docs/data/data-grid/filtering/server-side.md +++ b/docs/data/data-grid/filtering/server-side.md @@ -8,6 +8,10 @@ The example below demonstrates how to achieve server-side filtering. {{"demo": "ServerFilterGrid.js", "bg": "inline"}} +:::success +You can combine server-side filtering with [server-side sorting](/x/react-data-grid/sorting/#server-side-sorting) and [server-side pagination](/x/react-data-grid/pagination/#server-side-pagination) to avoid fetching more data than needed, since it's already processed outside of the Data Grid. +::: + ## API - [DataGrid](/x/api/data-grid/data-grid/) diff --git a/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.js b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.js new file mode 100644 index 0000000000000..24c7294a12c62 --- /dev/null +++ b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.js @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { createFakeServer } from '@mui/x-data-grid-generator'; + +const SERVER_OPTIONS = { + useCursorPagination: false, +}; + +const { useQuery, ...data } = createFakeServer({}, SERVER_OPTIONS); + +export default function ServerPaginationFilterSortGrid() { + const [paginationModel, setPaginationModel] = React.useState({ + page: 0, + pageSize: 5, + }); + const [sortModel, setSortModel] = React.useState([]); + const [filterModel, setFilterModel] = React.useState({ + items: [], + }); + const queryOptions = React.useMemo( + () => ({ ...paginationModel, sortModel, filterModel }), + [paginationModel, sortModel, filterModel], + ); + const { isLoading, rows, pageInfo } = useQuery(queryOptions); + + // Some API clients return undefined while loading + // 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 ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx new file mode 100644 index 0000000000000..37a361d6424ca --- /dev/null +++ b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { DataGrid, GridSortModel, GridFilterModel } from '@mui/x-data-grid'; +import { createFakeServer } from '@mui/x-data-grid-generator'; + +const SERVER_OPTIONS = { + useCursorPagination: false, +}; + +const { useQuery, ...data } = createFakeServer({}, SERVER_OPTIONS); + +export default function ServerPaginationFilterSortGrid() { + const [paginationModel, setPaginationModel] = React.useState({ + page: 0, + pageSize: 5, + }); + const [sortModel, setSortModel] = React.useState([]); + const [filterModel, setFilterModel] = React.useState({ + items: [], + }); + const queryOptions = React.useMemo( + () => ({ ...paginationModel, sortModel, filterModel }), + [paginationModel, sortModel, filterModel], + ); + const { isLoading, rows, pageInfo } = useQuery(queryOptions); + + // Some API clients return undefined while loading + // 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 ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx.preview b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx.preview new file mode 100644 index 0000000000000..3a9a0aa25687f --- /dev/null +++ b/docs/data/data-grid/pagination/ServerPaginationFilterSortGrid.tsx.preview @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/pagination/pagination.md b/docs/data/data-grid/pagination/pagination.md index 7259d7fca5b02..17878263cc46b 100644 --- a/docs/data/data-grid/pagination/pagination.md +++ b/docs/data/data-grid/pagination/pagination.md @@ -103,6 +103,14 @@ By default, the pagination is handled on the client. This means you have to give the rows of all pages to the Data Grid. If your dataset is too big, and you want to fetch the pages on demand, you can use server-side pagination. +:::warning +If you enable server-side pagination with no other server-side features, then the Data Grid will only be provided with partial data for filtering and sorting. +To be able to work with the entire dataset, you must also implement [server-side filtering](/x/react-data-grid/filtering/server-side/) and [server-side sorting](/x/react-data-grid/sorting/#server-side-sorting). +The demo below does exactly that. +::: + +{{"demo": "ServerPaginationFilterSortGrid.js", "bg": "inline"}} + In general, the server-side pagination could be categorized into two types: - Index-based pagination diff --git a/docs/data/data-grid/sorting/SortingWithPageReset.js b/docs/data/data-grid/sorting/SortingWithPageReset.js new file mode 100644 index 0000000000000..2ddb05e5b2047 --- /dev/null +++ b/docs/data/data-grid/sorting/SortingWithPageReset.js @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +export default function SortingWithPageReset() { + const { data, loading } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/sorting/SortingWithPageReset.tsx b/docs/data/data-grid/sorting/SortingWithPageReset.tsx new file mode 100644 index 0000000000000..2ddb05e5b2047 --- /dev/null +++ b/docs/data/data-grid/sorting/SortingWithPageReset.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { DataGrid } from '@mui/x-data-grid'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin']; + +export default function SortingWithPageReset() { + const { data, loading } = useDemoData({ + dataSet: 'Employee', + visibleFields: VISIBLE_FIELDS, + rowLength: 100, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/sorting/SortingWithPageReset.tsx.preview b/docs/data/data-grid/sorting/SortingWithPageReset.tsx.preview new file mode 100644 index 0000000000000..b0086d4b64c1d --- /dev/null +++ b/docs/data/data-grid/sorting/SortingWithPageReset.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/sorting/sorting.md b/docs/data/data-grid/sorting/sorting.md index eb6c4f62f8bd2..18cd30a300f77 100644 --- a/docs/data/data-grid/sorting/sorting.md +++ b/docs/data/data-grid/sorting/sorting.md @@ -91,6 +91,13 @@ In the following demo, the `firstName` column is not sortable by the default gri {{"demo": "ReadOnlySortingGrid.js", "bg": "inline", "defaultCodeOpen": false}} +## Reset page on sorting + +By default, sorting does not change the current page. +To send the user back to the first page when a new sort is applied, use the `resetPageOnSortFilter` prop. + +{{"demo": "SortingWithPageReset.js", "bg": "inline", "defaultCodeOpen": false}} + ## Custom comparator A comparator determines how two cell values should be sorted. @@ -164,6 +171,10 @@ Sorting can be run server-side by setting the `sortingMode` prop to `server`, an {{"demo": "ServerSortingGrid.js", "bg": "inline"}} +:::success +You can combine server-side sorting with [server-side filtering](/x/react-data-grid/filtering/server-side/) and [server-side pagination](/x/react-data-grid/pagination/#server-side-pagination) to avoid fetching more data than needed, since it's already processed outside of the Data Grid. +::: + ## apiRef :::warning diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index d841582c98866..5c9b6ad0d1233 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -580,6 +580,7 @@ "returned": "Promise | R" } }, + "resetPageOnSortFilter": { "type": { "name": "bool" }, "default": "false" }, "resizeThrottleMs": { "type": { "name": "number" }, "default": "60" }, "rowBufferPx": { "type": { "name": "number" }, "default": "150" }, "rowCount": { "type": { "name": "number" } }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 2682a7d8d1300..82e451f0b49f2 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -520,6 +520,7 @@ "returned": "Promise | R" } }, + "resetPageOnSortFilter": { "type": { "name": "bool" }, "default": "false" }, "resizeThrottleMs": { "type": { "name": "number" }, "default": "60" }, "rowBufferPx": { "type": { "name": "number" }, "default": "150" }, "rowCount": { "type": { "name": "number" } }, diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index eb747838952ca..2225c4f76f7fe 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -436,6 +436,7 @@ "returned": "Promise | R" } }, + "resetPageOnSortFilter": { "type": { "name": "bool" }, "default": "false" }, "resizeThrottleMs": { "type": { "name": "number" }, "default": "60" }, "rowBufferPx": { "type": { "name": "number" }, "default": "150" }, "rowCount": { "type": { "name": "number" } }, diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index 15904b669cfb3..e507c4f395bcd 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -600,6 +600,9 @@ "Promise | R": "The final values to update the row." } }, + "resetPageOnSortFilter": { + "description": "If true, the page is set to 0 after each sorting or filtering. This prop will be removed in the next major version and resetting the page will become the default behavior." + }, "resizeThrottleMs": { "description": "The milliseconds throttle delay for resizing the grid." }, "rowBufferPx": { "description": "Row region in pixels to render before/after the viewport" }, "rowCount": { diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index bc84b36fabbf0..d267b6ea85223 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -546,6 +546,9 @@ "Promise | R": "The final values to update the row." } }, + "resetPageOnSortFilter": { + "description": "If true, the page is set to 0 after each sorting or filtering. This prop will be removed in the next major version and resetting the page will become the default behavior." + }, "resizeThrottleMs": { "description": "The milliseconds throttle delay for resizing the grid." }, "rowBufferPx": { "description": "Row region in pixels to render before/after the viewport" }, "rowCount": { diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 70cfd2fa8a474..569d3272a60c3 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -448,6 +448,9 @@ "Promise | R": "The final values to update the row." } }, + "resetPageOnSortFilter": { + "description": "If true, the page is set to 0 after each sorting or filtering. This prop will be removed in the next major version and resetting the page will become the default behavior." + }, "resizeThrottleMs": { "description": "The milliseconds throttle delay for resizing the grid." }, "rowBufferPx": { "description": "Row region in pixels to render before/after the viewport" }, "rowCount": { diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 54a8a1f97741d..e40d38077596a 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -922,6 +922,12 @@ DataGridPremiumRaw.propTypes = { * @returns {Promise | R} The final values to update the row. */ processRowUpdate: PropTypes.func, + /** + * If `true`, the page is set to 0 after each sorting or filtering. + * This prop will be removed in the next major version and resetting the page will become the default behavior. + * @default false + */ + resetPageOnSortFilter: PropTypes.bool, /** * The milliseconds throttle delay for resizing the grid. * @default 60 diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index bb3a8ba403026..d40d64b331316 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -838,6 +838,12 @@ DataGridProRaw.propTypes = { * @returns {Promise | R} The final values to update the row. */ processRowUpdate: PropTypes.func, + /** + * If `true`, the page is set to 0 after each sorting or filtering. + * This prop will be removed in the next major version and resetting the page will become the default behavior. + * @default false + */ + resetPageOnSortFilter: PropTypes.bool, /** * The milliseconds throttle delay for resizing the grid. * @default 60 diff --git a/packages/x-data-grid/src/DataGrid/DataGrid.tsx b/packages/x-data-grid/src/DataGrid/DataGrid.tsx index bde3fca7668ce..6086f7cc88dd0 100644 --- a/packages/x-data-grid/src/DataGrid/DataGrid.tsx +++ b/packages/x-data-grid/src/DataGrid/DataGrid.tsx @@ -687,6 +687,12 @@ DataGridRaw.propTypes = { * @returns {Promise | R} The final values to update the row. */ processRowUpdate: PropTypes.func, + /** + * If `true`, the page is set to 0 after each sorting or filtering. + * This prop will be removed in the next major version and resetting the page will become the default behavior. + * @default false + */ + resetPageOnSortFilter: PropTypes.bool, /** * The milliseconds throttle delay for resizing the grid. * @default 60 diff --git a/packages/x-data-grid/src/constants/dataGridPropsDefaultValues.ts b/packages/x-data-grid/src/constants/dataGridPropsDefaultValues.ts index 4c69790cebb67..85e093701139b 100644 --- a/packages/x-data-grid/src/constants/dataGridPropsDefaultValues.ts +++ b/packages/x-data-grid/src/constants/dataGridPropsDefaultValues.ts @@ -46,6 +46,7 @@ export const DATA_GRID_PROPS_DEFAULT_VALUES: DataGridPropsWithDefaultValues = { pageSizeOptions: [25, 50, 100], pagination: false, paginationMode: 'client', + resetPageOnSortFilter: false, resizeThrottleMs: 60, rowBufferPx: 150, rowHeight: 52, diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts index c331fa6af6933..d6efed247c3b4 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPaginationModel.ts @@ -5,6 +5,11 @@ import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPaginationModelApi, GridPaginationState } from './gridPaginationInterfaces'; import { GridEventListener } from '../../../models/events'; import { GridPaginationModel } from '../../../models/gridPaginationProps'; +import { GridFilterModel } from '../../../models/gridFilterModel'; +import { + gridFilterModelSelector, + gridFilterActiveItemsSelector, +} from '../filter/gridFilterSelector'; import { gridDensityFactorSelector } from '../density'; import { useGridLogger, @@ -12,6 +17,7 @@ import { useGridApiMethod, useGridApiEventHandler, } from '../../utils'; +import { isDeepEqual, runIf } from '../../../utils/utils'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { gridPageCountSelector, gridPaginationModelSelector } from './gridPaginationSelector'; import { @@ -67,11 +73,13 @@ export const useGridPaginationModel = ( | 'pagination' | 'signature' | 'rowHeight' + | 'resetPageOnSortFilter' >, ) => { const logger = useGridLogger(apiRef, 'useGridPaginationModel'); - const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); + const previousFilterModel = React.useRef(gridFilterModelSelector(apiRef)); + const rowHeight = Math.floor(props.rowHeight * densityFactor); apiRef.current.registerControlState({ stateId: 'paginationModel', @@ -242,7 +250,12 @@ export const useGridPaginationModel = ( if (newRowCount == null) { return; } + const paginationModel = gridPaginationModelSelector(apiRef); + if (paginationModel.page === 0) { + return; + } + const pageCount = gridPageCountSelector(apiRef); if (paginationModel.page > pageCount - 1) { apiRef.current.setPage(Math.max(0, pageCount - 1)); @@ -251,9 +264,59 @@ export const useGridPaginationModel = ( [apiRef], ); + /** + * Goes to the first row of the grid + */ + const navigateToStart = React.useCallback(() => { + const paginationModel = gridPaginationModelSelector(apiRef); + if (paginationModel.page !== 0) { + apiRef.current.setPage(0); + } + + // If the page was not changed it might be needed to scroll to the top + const scrollPosition = apiRef.current.getScrollPosition(); + if (scrollPosition.top !== 0) { + apiRef.current.scroll({ top: 0 }); + } + }, [apiRef]); + + /** + * Resets the page only if the active items or quick filter has changed from the last time. + * This is to avoid resetting the page when the filter model is changed + * because of and update of the operator from an item that does not have the value + * or reseting when the filter panel is just opened + */ + const handleFilterModelChange = React.useCallback>( + (filterModel) => { + const currentActiveFilters = { + ...filterModel, + // replace items with the active items + items: gridFilterActiveItemsSelector(apiRef), + }; + + if (isDeepEqual(currentActiveFilters, previousFilterModel.current)) { + return; + } + + previousFilterModel.current = currentActiveFilters; + navigateToStart(); + }, + [apiRef, navigateToStart], + ); + useGridApiEventHandler(apiRef, 'viewportInnerSizeChange', handleUpdateAutoPageSize); useGridApiEventHandler(apiRef, 'paginationModelChange', handlePaginationModelChange); useGridApiEventHandler(apiRef, 'rowCountChange', handleRowCountChange); + useGridApiEventHandler( + apiRef, + 'sortModelChange', + runIf(props.resetPageOnSortFilter, navigateToStart), + ); + useGridApiEventHandler( + apiRef, + 'filterModelChange', + runIf(props.resetPageOnSortFilter, handleFilterModelChange), + ); /** * EFFECTS diff --git a/packages/x-data-grid/src/models/props/DataGridProps.ts b/packages/x-data-grid/src/models/props/DataGridProps.ts index 468c2fde0abc3..ff8ac24293f7a 100644 --- a/packages/x-data-grid/src/models/props/DataGridProps.ts +++ b/packages/x-data-grid/src/models/props/DataGridProps.ts @@ -295,6 +295,12 @@ export interface DataGridPropsWithDefaultValues - Pagination', () => { const { render } = createRenderer(); + let apiRef: RefObject; function BaselineTestCase(props: Omit & { height?: number }) { const { height = 300, ...other } = props; - const basicData = useBasicDemoData(20, 2); + apiRef = useGridApiRef(); + const basicData = useBasicDemoData(100, 2); return (
- +
); } @@ -265,19 +274,13 @@ describe(' - Pagination', () => { }); it('should throw if pageSize exceeds 100', () => { - let apiRef: RefObject; - function TestCase() { - apiRef = useGridApiRef(); - return ( - - ); - } - render(); - expect(() => apiRef.current.setPageSize(101)).to.throw( + render( + , + ); + expect(() => apiRef.current?.setPageSize(101)).to.throw( /`pageSize` cannot exceed 100 in the MIT version of the DataGrid./, ); }); @@ -628,6 +631,90 @@ describe(' - Pagination', () => { }); }); + describe('resetPageOnSortFilter prop', () => { + it('should reset page to 0 and scroll to top if sort or filter is applied and `resetPageOnSortFilter` is `true`', () => { + const { setProps } = render( + , + ); + + const randomScrollTopPostion = 500; + + act(() => { + apiRef.current?.setPage(1); + apiRef.current!.scroll({ top: randomScrollTopPostion }); + }); + expect(apiRef.current!.state.pagination.paginationModel.page).to.equal(1); + expect(apiRef.current!.getScrollPosition().top).to.equal(randomScrollTopPostion); + + act(() => { + apiRef.current?.sortColumn('id', 'desc'); + apiRef.current?.setFilterModel({ + items: [ + { + field: 'id', + value: '1', + operator: '>=', + }, + ], + }); + }); + + // page and the scroll position stays the same after sorting and filtering + expect(apiRef.current!.state.pagination.paginationModel.page).to.equal(1); + expect(apiRef.current!.getScrollPosition().top).to.equal(randomScrollTopPostion); + + // enable reset + setProps({ + resetPageOnSortFilter: true, + }); + + act(() => { + apiRef.current?.sortColumn('id', 'asc'); + }); + // page is reset to 0 after sorting + expect(apiRef.current!.state.pagination.paginationModel.page).to.equal(0); + expect(apiRef.current!.getScrollPosition().top).to.equal(0); + + // scroll but stay on the same page + act(() => { + apiRef.current!.scroll({ top: randomScrollTopPostion }); + }); + expect(apiRef.current!.getScrollPosition().top).to.equal(randomScrollTopPostion); + + act(() => { + apiRef.current!.sortColumn('id', 'desc'); + }); + expect(apiRef.current!.getScrollPosition().top).to.equal(0); + + // move to the next page again and scroll + act(() => { + apiRef.current?.setPage(1); + apiRef.current!.scroll({ top: randomScrollTopPostion }); + }); + expect(apiRef.current!.state.pagination.paginationModel.page).to.equal(1); + expect(apiRef.current!.getScrollPosition().top).to.equal(randomScrollTopPostion); + + act(() => { + apiRef.current?.setFilterModel({ + items: [ + { + field: 'id', + value: '1', + operator: '>=', + }, + ], + }); + }); + + // page and scroll position are reset filtering + expect(apiRef.current!.state.pagination.paginationModel.page).to.equal(0); + expect(apiRef.current!.getScrollPosition().top).to.equal(0); + }); + }); + it('should make the first cell focusable after changing the page', () => { render(