(
+ undefined,
+ );
+ const [isMounted, setIsMounted] = React.useState(true);
+
+ const unMountGrid = React.useCallback((stateToState: GridInitialState) => {
+ setIsMounted(false);
+ setSavedState(stateToState);
+ }, []);
+
+ const restoreGrid = () => setIsMounted(true);
+
+ if (isMounted) {
+ return (
+
+
+
+
+ {!!savedState && (
+
+ Initial state: {JSON.stringify(savedState)}
+
+ )}
+
+ );
+ }
+
+ return ;
+}
diff --git a/docs/src/pages/components/data-grid/state/RestoreStateInitialState.tsx.preview b/docs/src/pages/components/data-grid/state/RestoreStateInitialState.tsx.preview
new file mode 100644
index 000000000000..03bd4701ede7
--- /dev/null
+++ b/docs/src/pages/components/data-grid/state/RestoreStateInitialState.tsx.preview
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/src/pages/components/data-grid/state/state.md b/docs/src/pages/components/data-grid/state/state.md
index fa87ba6602ac..b7a8f444a582 100644
--- a/docs/src/pages/components/data-grid/state/state.md
+++ b/docs/src/pages/components/data-grid/state/state.md
@@ -9,6 +9,7 @@ title: Data Grid - State
## Initialize the state
Some state keys can be initialized with the `initialState` prop.
+This prop has the same format as the returned value of `apiRef.current.exportState()`.
> ⚠️ The `initialState` can only be used to set the initial value of the state, the grid will not react if you change the `initialState` value later on.
>
@@ -61,6 +62,37 @@ Some selectors are yet to be documented.
>
> 👍 Upvote [issue #820](https://github.com/mui-org/material-ui-x/issues/820) if you want to see it land faster.
+[//]: # 'The current state of the grid can be exported using `apiRef.current.exportState()`.'
+[//]: # 'It can then be restored by either passing it to the `initialState` prop or to the `apiRef.current.restoreState()` method.'
+[//]: #
+[//]: # 'Watch out for controlled models and their callbacks (`onFilterModelChange` if you use `filterModel` for instance), the grid will call those callbacks when restoring the state.'
+[//]: # 'But if the callback is not defined or if calling it does not update the prop value, then the restored value will not be applied.'
+[//]: #
+[//]: # '### Restore the state with `initialState`'
+[//]: #
+[//]: # '> ⚠️ If you restore the page using `initialState` before the data is fetched, the grid will automatically move to the 1st page.'
+[//]: #
+[//]: # '{{"demo": "pages/components/data-grid/state/RestoreStateInitialState.js", "bg": "inline", "defaultCodeOpen": false}}'
+[//]: #
+[//]: # '### Restore the state with `apiRef` [](https://mui.com/store/items/material-ui-pro/)'
+[//]: #
+[//]: # '{{"demo": "pages/components/data-grid/state/RestoreStateApiRef.js", "bg": "inline", "defaultCodeOpen": false}}'
+[//]: #
+[//]: # '#### Restore part of the state'
+[//]: #
+[//]: # 'It is possible to restore specific properties of the state using the `apiRef.current.restoreState()` method.'
+[//]: # 'For instance, to only restore the pinned columns:'
+[//]: #
+[//]: # '```ts'
+[//]: # 'apiRef.current.restoreState({'
+[//]: # " pinnedColumns: ['brand'],"
+[//]: # '});'
+[//]: # '```'
+[//]: #
+[//]: # '> ⚠️ Most of the state keys are not fully independent.'
+[//]: # '>'
+[//]: # '> Restoring the pagination without restoring the filters or the sorting will work, but the rows displayed after the re-import will not be the same as before the export.'
+
## API
- [DataGrid](/api/data-grid/data-grid/)
diff --git a/packages/grid/_modules_/grid/hooks/core/preProcessing/gridPreProcessingApi.ts b/packages/grid/_modules_/grid/hooks/core/preProcessing/gridPreProcessingApi.ts
index 9245d0ceb279..88645c196eab 100644
--- a/packages/grid/_modules_/grid/hooks/core/preProcessing/gridPreProcessingApi.ts
+++ b/packages/grid/_modules_/grid/hooks/core/preProcessing/gridPreProcessingApi.ts
@@ -1,4 +1,13 @@
-import { GridCellIndexCoordinates, GridColDef, GridScrollParams } from '../../../models';
+import {
+ GridCellIndexCoordinates,
+ GridColDef,
+ GridInitialState,
+ GridScrollParams,
+} from '../../../models';
+import {
+ GridRestoreStatePreProcessingContext,
+ GridRestoreStatePreProcessingValue,
+} from '../../features/statePersistence';
import { GridFilteringMethodCollection } from '../../features/filter/gridFilterState';
import { GridSortingMethodCollection } from '../../features/sorting/gridSortingState';
import { GridCanBeReorderedPreProcessingContext } from '../../features/columnReorder/columnReorderInterfaces';
@@ -23,6 +32,11 @@ interface GridPreProcessingGroupLookup {
};
filteringMethod: { value: GridFilteringMethodCollection };
sortingMethod: { value: GridSortingMethodCollection };
+ exportState: { value: GridInitialState };
+ restoreState: {
+ value: GridRestoreStatePreProcessingValue;
+ context: GridRestoreStatePreProcessingContext;
+ };
}
export type GridPreProcessor = (
diff --git a/packages/grid/_modules_/grid/hooks/features/columnPinning/useGridColumnPinning.tsx b/packages/grid/_modules_/grid/hooks/features/columnPinning/useGridColumnPinning.tsx
index e88737ff1531..6154efbfc7ad 100644
--- a/packages/grid/_modules_/grid/hooks/features/columnPinning/useGridColumnPinning.tsx
+++ b/packages/grid/_modules_/grid/hooks/features/columnPinning/useGridColumnPinning.tsx
@@ -14,16 +14,25 @@ import { gridClasses } from '../../../gridClasses';
import { useGridRegisterPreProcessor } from '../../core/preProcessing/useGridRegisterPreProcessor';
import { GridColumnPinningMenuItems } from '../../../components/menu/columnMenu/GridColumnPinningMenuItems';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
-import { GridColumnPinningApi, GridPinnedPosition } from '../../../models/api/gridColumnPinningApi';
+import {
+ GridColumnPinningApi,
+ GridPinnedColumns,
+ GridPinnedPosition,
+} from '../../../models/api/gridColumnPinningApi';
import { gridPinnedColumnsSelector } from './columnPinningSelector';
import { useGridStateInit } from '../../utils/useGridStateInit';
import { useGridSelector } from '../../utils/useGridSelector';
import { filterColumns } from '../../../../../x-data-grid-pro/src/DataGridProVirtualScroller';
import { GridRowParams } from '../../../models/params/gridRowParams';
import { MuiEvent } from '../../../models/muiEvent';
+import { GridState } from '../../../models';
const Divider = () => event.stopPropagation()} />;
+const mergeStateWithPinnedColumns =
+ (pinnedColumns: GridPinnedColumns) =>
+ (state: GridState): GridState => ({ ...state, pinnedColumns });
+
export const useGridColumnPinning = (
apiRef: GridApiRef,
props: Pick<
@@ -31,13 +40,23 @@ export const useGridColumnPinning = (
'initialState' | 'disableColumnPinning' | 'pinnedColumns' | 'onPinnedColumnsChange'
>,
): void => {
- useGridStateInit(apiRef, (state) => ({
- ...state,
- pinnedColumns: {
- left: !props.disableColumnPinning ? props.initialState?.pinnedColumns?.left : undefined,
- right: !props.disableColumnPinning ? props.initialState?.pinnedColumns?.right : undefined,
- },
- }));
+ useGridStateInit(apiRef, (state) => {
+ let model: GridPinnedColumns;
+ if (props.disableColumnPinning) {
+ model = {};
+ } else if (props.pinnedColumns) {
+ model = props.pinnedColumns;
+ } else if (props.initialState?.pinnedColumns) {
+ model = props.initialState?.pinnedColumns;
+ } else {
+ model = {};
+ }
+
+ return {
+ ...state,
+ pinnedColumns: model,
+ };
+ });
const pinnedColumns = useGridSelector(apiRef, gridPinnedColumnsSelector);
// Each visible row (not to be confused with a filter result) is composed of a central .MuiDataGrid-row element
@@ -207,10 +226,42 @@ export const useGridColumnPinning = (
[apiRef, pinnedColumns],
);
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const pinnedColumnsToExport = gridPinnedColumnsSelector(apiRef.current.state);
+ if (
+ (!pinnedColumnsToExport.left || pinnedColumnsToExport.left.length === 0) &&
+ (!pinnedColumnsToExport.right || pinnedColumnsToExport.right.length === 0)
+ ) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ pinnedColumns: pinnedColumnsToExport,
+ };
+ },
+ [apiRef],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ const newPinnedColumns = context.stateToRestore.pinnedColumns;
+ if (newPinnedColumns != null) {
+ apiRef.current.setState(mergeStateWithPinnedColumns(newPinnedColumns));
+ }
+
+ return params;
+ },
+ [apiRef],
+ );
+
useGridRegisterPreProcessor(apiRef, 'scrollToIndexes', calculateScrollLeft);
useGridRegisterPreProcessor(apiRef, 'columnMenu', addColumnMenuButtons);
useGridRegisterPreProcessor(apiRef, 'hydrateColumns', reorderPinnedColumns);
useGridRegisterPreProcessor(apiRef, 'canBeReordered', checkIfCanBeReordered);
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
apiRef.current.unstable_updateControlState({
stateId: 'pinnedColumns',
@@ -278,7 +329,7 @@ export const useGridColumnPinning = (
const setPinnedColumns = React.useCallback(
(newPinnedColumns) => {
checkIfEnabled('setPinnedColumns');
- apiRef.current.setState((state) => ({ ...state, pinnedColumns: newPinnedColumns }));
+ apiRef.current.setState(mergeStateWithPinnedColumns(newPinnedColumns));
apiRef.current.forceUpdate();
},
[apiRef, checkIfEnabled],
diff --git a/packages/grid/_modules_/grid/hooks/features/columnReorder/columnReorderState.ts b/packages/grid/_modules_/grid/hooks/features/columnReorder/columnReorderState.ts
deleted file mode 100644
index b08d7f315276..000000000000
--- a/packages/grid/_modules_/grid/hooks/features/columnReorder/columnReorderState.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface GridColumnReorderState {
- dragCol: string;
-}
diff --git a/packages/grid/_modules_/grid/hooks/features/columns/gridColumnsUtils.ts b/packages/grid/_modules_/grid/hooks/features/columns/gridColumnsUtils.ts
index aa662640349f..6f085d0450e2 100644
--- a/packages/grid/_modules_/grid/hooks/features/columns/gridColumnsUtils.ts
+++ b/packages/grid/_modules_/grid/hooks/features/columns/gridColumnsUtils.ts
@@ -11,6 +11,7 @@ import {
GridColDef,
GridColType,
GridColumnTypesRecord,
+ GridState,
GridStateColDef,
} from '../../../models';
import { gridColumnsSelector, gridColumnVisibilityModelSelector } from './gridColumnsSelector';
@@ -208,3 +209,10 @@ export const createColumnsState = ({
apiRef.current.getRootDimensions?.()?.viewportInnerSize.width ?? 0,
);
};
+
+export const setColumnsState =
+ (columnsState: GridColumnsState) =>
+ (state: GridState): GridState => ({
+ ...state,
+ columns: columnsState,
+ });
diff --git a/packages/grid/_modules_/grid/hooks/features/columns/useGridColumns.ts b/packages/grid/_modules_/grid/hooks/features/columns/useGridColumns.ts
index cc7bfe8473bb..215a3a9052b1 100644
--- a/packages/grid/_modules_/grid/hooks/features/columns/useGridColumns.ts
+++ b/packages/grid/_modules_/grid/hooks/features/columns/useGridColumns.ts
@@ -21,8 +21,14 @@ import {
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { useGridStateInit } from '../../utils/useGridStateInit';
import { GridColumnVisibilityChangeParams } from '../../../models';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
import { GridColumnsState, GridColumnVisibilityModel } from './gridColumnsInterfaces';
-import { hydrateColumnsWidth, computeColumnTypes, createColumnsState } from './gridColumnsUtils';
+import {
+ hydrateColumnsWidth,
+ computeColumnTypes,
+ createColumnsState,
+ setColumnsState,
+} from './gridColumnsUtils';
/**
* @requires useGridParamsApi (method)
@@ -87,7 +93,7 @@ export function useGridColumns(
(columnsState: GridColumnsState) => {
logger.debug('Updating columns state.');
- apiRef.current.setState((state) => ({ ...state, columns: columnsState }));
+ apiRef.current.setState(setColumnsState(columnsState));
apiRef.current.forceUpdate();
apiRef.current.publishEvent(GridEvents.columnsChange, columnsState.all);
},
@@ -268,6 +274,59 @@ export function useGridColumns(
useGridApiMethod(apiRef, columnApi, 'GridColumnApi');
+ /**
+ * PRE-PROCESSING
+ */
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ if (!shouldUseVisibleColumnModel) {
+ return prevState;
+ }
+
+ const columnVisibilityModelToExport = gridColumnVisibilityModelSelector(apiRef.current.state);
+ const hasHiddenColumns = Object.values(columnVisibilityModelToExport).some(
+ (value) => value === false,
+ );
+ if (!hasHiddenColumns) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ columns: {
+ columnVisibilityModel: columnVisibilityModelToExport,
+ },
+ };
+ },
+ [apiRef, shouldUseVisibleColumnModel],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ if (!shouldUseVisibleColumnModel) {
+ return params;
+ }
+
+ const columnVisibilityModel = context.stateToRestore.columns?.columnVisibilityModel;
+ if (columnVisibilityModel != null) {
+ const columnsState = createColumnsState({
+ apiRef,
+ columnsTypes,
+ columnsToUpsert: [],
+ shouldRegenColumnVisibilityModelFromColumns: false,
+ currentColumnVisibilityModel: columnVisibilityModel,
+ reset: false,
+ });
+ apiRef.current.setState(setColumnsState(columnsState));
+ }
+ return params;
+ },
+ [apiRef, shouldUseVisibleColumnModel, columnsTypes],
+ );
+
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
+
/**
* EVENTS
*/
diff --git a/packages/grid/_modules_/grid/hooks/features/filter/gridFilterUtils.ts b/packages/grid/_modules_/grid/hooks/features/filter/gridFilterUtils.ts
index 53f1a9d5014a..a29c9b4e385d 100644
--- a/packages/grid/_modules_/grid/hooks/features/filter/gridFilterUtils.ts
+++ b/packages/grid/_modules_/grid/hooks/features/filter/gridFilterUtils.ts
@@ -4,6 +4,7 @@ import {
GridFilterModel,
GridLinkOperator,
GridRowId,
+ GridState,
} from '../../../models';
import { GridAggregatedFilterItemApplier } from './gridFilterState';
@@ -12,6 +13,24 @@ type GridFilterItemApplier = {
item: GridFilterItem;
};
+export const mergeStateWithFilterModel = (
+ filterModel: GridFilterModel,
+ disableMultipleColumnsFiltering: boolean,
+) => {
+ const cleanFilterModel = { ...filterModel };
+ if (cleanFilterModel.items.length > 1 && disableMultipleColumnsFiltering) {
+ cleanFilterModel.items = [cleanFilterModel.items[0]];
+ }
+
+ return (state: GridState): GridState => ({
+ ...state,
+ filter: {
+ ...state.filter,
+ filterModel,
+ },
+ });
+};
+
/**
* Adds default values to the optional fields of a filter items.
* @param {GridFilterItem} item The raw filter item.
diff --git a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts
index de7d1e7ef819..ef5c0a63f58c 100644
--- a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts
+++ b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts
@@ -21,8 +21,13 @@ import { gridFilterModelSelector, gridVisibleSortedRowEntriesSelector } from './
import { useGridStateInit } from '../../utils/useGridStateInit';
import { useFirstRender } from '../../utils/useFirstRender';
import { gridRowIdsSelector, gridRowGroupingNameSelector } from '../rows';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
import { useGridRegisterFilteringMethod } from './useGridRegisterFilteringMethod';
-import { buildAggregatedFilterApplier, cleanFilterItem } from './gridFilterUtils';
+import {
+ buildAggregatedFilterApplier,
+ cleanFilterItem,
+ mergeStateWithFilterModel,
+} from './gridFilterUtils';
const checkFilterModelValidity = (model: GridFilterModel) => {
if (model.items.length > 1) {
@@ -203,18 +208,10 @@ export const useGridFilter = (
if (currentModel !== model) {
checkFilterModelValidity(model);
- if (model.items.length > 1 && props.disableMultipleColumnsFiltering) {
- model.items = [model.items[0]];
- }
-
logger.debug('Setting filter model');
- apiRef.current.setState((state) => ({
- ...state,
- filter: {
- ...state.filter,
- filterModel: model,
- },
- }));
+ apiRef.current.setState(
+ mergeStateWithFilterModel(model, props.disableMultipleColumnsFiltering),
+ );
apiRef.current.unstable_applyFilters();
}
},
@@ -242,6 +239,44 @@ export const useGridFilter = (
/**
* PRE-PROCESSING
*/
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const filterModelToExport = gridFilterModelSelector(apiRef.current.state);
+ if (
+ filterModelToExport.items.length === 0 &&
+ filterModelToExport.linkOperator === getDefaultGridFilterModel().linkOperator
+ ) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ filter: {
+ filterModel: filterModelToExport,
+ },
+ };
+ },
+ [apiRef],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ const filterModel = context.stateToRestore.filter?.filterModel;
+ if (filterModel == null) {
+ return params;
+ }
+ apiRef.current.setState(
+ mergeStateWithFilterModel(filterModel, props.disableMultipleColumnsFiltering),
+ );
+
+ return {
+ ...params,
+ callbacks: [...params.callbacks, apiRef.current.unstable_applyFilters],
+ };
+ },
+ [apiRef, props.disableMultipleColumnsFiltering],
+ );
+
const flatFilteringMethod = React.useCallback(
(params) => {
if (props.filterMode === GridFeatureModeConstant.client && params.isRowMatchingFilters) {
@@ -268,6 +303,8 @@ export const useGridFilter = (
[apiRef, props.filterMode],
);
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
useGridRegisterFilteringMethod(apiRef, 'none', flatFilteringMethod);
/**
diff --git a/packages/grid/_modules_/grid/hooks/features/pagination/useGridPage.ts b/packages/grid/_modules_/grid/hooks/features/pagination/useGridPage.ts
index aeef97fbe6da..22be7a94570f 100644
--- a/packages/grid/_modules_/grid/hooks/features/pagination/useGridPage.ts
+++ b/packages/grid/_modules_/grid/hooks/features/pagination/useGridPage.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { GridApiRef } from '../../../models';
+import { GridApiRef, GridState } from '../../../models';
import {
useGridLogger,
useGridSelector,
@@ -12,6 +12,7 @@ import { GridPageApi, GridPaginationState } from './gridPaginationInterfaces';
import { gridVisibleTopLevelRowCountSelector } from '../filter';
import { useGridStateInit } from '../../utils/useGridStateInit';
import { gridPageSelector } from './gridPaginationSelector';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
const getPageCount = (rowCount: number, pageSize: number): number => {
if (pageSize > 0 && rowCount > 0) {
@@ -32,6 +33,16 @@ const applyValidPage = (paginationState: GridPaginationState): GridPaginationSta
};
};
+const mergeStateWithPage =
+ (page: number) =>
+ (state: GridState): GridState => ({
+ ...state,
+ pagination: applyValidPage({
+ ...state.pagination,
+ page,
+ }),
+ });
+
/**
* @requires useGridPageSize (state, event)
* @requires useGridFilter (state)
@@ -68,14 +79,7 @@ export const useGridPage = (
const setPage = React.useCallback(
(page) => {
logger.debug(`Setting page to ${page}`);
-
- apiRef.current.setState((state) => ({
- ...state,
- pagination: applyValidPage({
- ...state.pagination,
- page,
- }),
- }));
+ apiRef.current.setState(mergeStateWithPage(page));
apiRef.current.forceUpdate();
},
[apiRef, logger],
@@ -87,6 +91,41 @@ export const useGridPage = (
useGridApiMethod(apiRef, pageApi, 'GridPageApi');
+ /**
+ * PRE-PROCESSING
+ */
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const pageToExport = gridPageSelector(apiRef.current.state);
+ if (pageToExport === 0) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ pagination: {
+ ...prevState.pagination,
+ page: pageToExport,
+ },
+ };
+ },
+ [apiRef],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ // We apply the constraint even if the page did not change in case the pageSize changed.
+ const page =
+ context.stateToRestore.pagination?.page ?? gridPageSelector(apiRef.current.state);
+ apiRef.current.setState(mergeStateWithPage(page));
+ return params;
+ },
+ [apiRef],
+ );
+
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
+
/**
* EVENTS
*/
diff --git a/packages/grid/_modules_/grid/hooks/features/pagination/useGridPageSize.ts b/packages/grid/_modules_/grid/hooks/features/pagination/useGridPageSize.ts
index 482eaee4b357..8fade2b2845b 100644
--- a/packages/grid/_modules_/grid/hooks/features/pagination/useGridPageSize.ts
+++ b/packages/grid/_modules_/grid/hooks/features/pagination/useGridPageSize.ts
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { GridApiRef } from '../../../models';
+import { GridApiRef, GridState } from '../../../models';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridPageSizeApi } from './gridPaginationInterfaces';
import { GridEvents } from '../../../models/events';
@@ -12,6 +12,17 @@ import {
import { useGridStateInit } from '../../utils/useGridStateInit';
import { gridPageSizeSelector } from './gridPaginationSelector';
import { gridDensityRowHeightSelector } from '../density';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
+
+const mergeStateWithPageSize =
+ (pageSize: number) =>
+ (state: GridState): GridState => ({
+ ...state,
+ pagination: {
+ ...state.pagination,
+ pageSize,
+ },
+ });
/**
* @requires useGridDimensions (event) - can be after
@@ -27,16 +38,16 @@ export const useGridPageSize = (
const logger = useGridLogger(apiRef, 'useGridPageSize');
const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector);
+ const defaultPageSize = props.autoPageSize ? 0 : 100;
+
useGridStateInit(apiRef, (state) => {
let pageSize: number;
if (props.pageSize != null) {
pageSize = props.pageSize;
} else if (props.initialState?.pagination?.pageSize != null) {
pageSize = props.initialState.pagination.pageSize;
- } else if (props.autoPageSize) {
- pageSize = 0;
} else {
- pageSize = 100;
+ pageSize = defaultPageSize;
}
return {
@@ -66,13 +77,7 @@ export const useGridPageSize = (
logger.debug(`Setting page size to ${pageSize}`);
- apiRef.current.setState((state) => ({
- ...state,
- pagination: {
- ...state.pagination,
- pageSize,
- },
- }));
+ apiRef.current.setState(mergeStateWithPageSize(pageSize));
apiRef.current.forceUpdate();
},
[apiRef, logger],
@@ -84,6 +89,44 @@ export const useGridPageSize = (
useGridApiMethod(apiRef, pageSizeApi, 'GridPageSizeApi');
+ /**
+ * PRE-PROCESSING
+ */
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const pageSizeToExport = gridPageSizeSelector(apiRef.current.state);
+ if (pageSizeToExport === defaultPageSize) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ pagination: {
+ ...prevState.pagination,
+ pageSize: pageSizeToExport,
+ },
+ };
+ },
+ [apiRef, defaultPageSize],
+ );
+
+ /**
+ * TODO: Add error if `prop.autoHeight = true`
+ */
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ const pageSize = context.stateToRestore.pagination?.pageSize;
+ if (pageSize != null) {
+ apiRef.current.setState(mergeStateWithPageSize(pageSize));
+ }
+ return params;
+ },
+ [apiRef],
+ );
+
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
+
/**
* EVENTS
*/
diff --git a/packages/grid/_modules_/grid/hooks/features/preferencesPanel/useGridPreferencesPanel.ts b/packages/grid/_modules_/grid/hooks/features/preferencesPanel/useGridPreferencesPanel.ts
index 779aa211b236..199ac272d933 100644
--- a/packages/grid/_modules_/grid/hooks/features/preferencesPanel/useGridPreferencesPanel.ts
+++ b/packages/grid/_modules_/grid/hooks/features/preferencesPanel/useGridPreferencesPanel.ts
@@ -5,7 +5,12 @@ import { useGridLogger } from '../../utils/useGridLogger';
import { GridPreferencePanelsValue } from './gridPreferencePanelsValue';
import { useGridStateInit } from '../../utils/useGridStateInit';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
+import { gridPreferencePanelStateSelector } from './gridPreferencePanelSelector';
+/**
+ * TODO: Add a single `setPreferencePanel` method to avoid multiple `setState`
+ */
export const useGridPreferencesPanel = (
apiRef: GridApiRef,
props: Pick,
@@ -19,6 +24,9 @@ export const useGridPreferencesPanel = (
const hideTimeout = React.useRef();
const immediateTimeout = React.useRef();
+ /**
+ * API METHODS
+ */
const hidePreferences = React.useCallback(() => {
logger.debug('Hiding Preferences Panel');
apiRef.current.setState((state) => ({ ...state, preferencePanel: { open: false } }));
@@ -59,6 +67,45 @@ export const useGridPreferencesPanel = (
'ColumnMenuApi',
);
+ /**
+ * PRE-PROCESSING
+ */
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const preferencePanelToExport = gridPreferencePanelStateSelector(apiRef.current.state);
+ if (!preferencePanelToExport.open && !preferencePanelToExport.openedPanelValue) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ preferencePanel: preferencePanelToExport,
+ };
+ },
+ [apiRef],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ const preferencePanel = context.stateToRestore.preferencePanel;
+ if (preferencePanel != null) {
+ apiRef.current.setState((state) => ({
+ ...state,
+ preferencePanel,
+ }));
+ }
+
+ return params;
+ },
+ [apiRef],
+ );
+
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
+
+ /**
+ * EFFECTS
+ */
React.useEffect(() => {
return () => {
clearTimeout(hideTimeout.current);
diff --git a/packages/grid/_modules_/grid/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/grid/_modules_/grid/hooks/features/rowGrouping/gridRowGroupingUtils.ts
index 9ac467ea6926..2951fea33f3d 100644
--- a/packages/grid/_modules_/grid/hooks/features/rowGrouping/gridRowGroupingUtils.ts
+++ b/packages/grid/_modules_/grid/hooks/features/rowGrouping/gridRowGroupingUtils.ts
@@ -3,10 +3,12 @@ import {
GridRowId,
GridRowTreeConfig,
GridRowTreeNodeConfig,
+ GridState,
} from '../../../models';
import { GridFilterState } from '../filter';
import { DataGridProProcessedProps } from '../../../models/props/DataGridProProps';
import { GridAggregatedFilterItemApplier } from '../filter/gridFilterState';
+import { GridRowGroupingModel } from './gridRowGroupingInterfaces';
export const GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD = '__row_group_by_columns_group__';
@@ -146,3 +148,10 @@ export const getColDefOverrides = (
return groupingColDefProp;
};
+
+export const mergeStateWithRowGroupingModel =
+ (rowGroupingModel: GridRowGroupingModel) =>
+ (state: GridState): GridState => ({
+ ...state,
+ rowGrouping: { ...state.rowGrouping, model: rowGroupingModel },
+ });
diff --git a/packages/grid/_modules_/grid/hooks/features/rowGrouping/useGridRowGrouping.tsx b/packages/grid/_modules_/grid/hooks/features/rowGrouping/useGridRowGrouping.tsx
index 7296fb4005cd..69b8c8efbabd 100644
--- a/packages/grid/_modules_/grid/hooks/features/rowGrouping/useGridRowGrouping.tsx
+++ b/packages/grid/_modules_/grid/hooks/features/rowGrouping/useGridRowGrouping.tsx
@@ -25,13 +25,14 @@ import {
getColDefOverrides,
GROUPING_COLUMNS_FEATURE_NAME,
isGroupingColumn,
+ mergeStateWithRowGroupingModel,
} from './gridRowGroupingUtils';
import {
createGroupingColDefForOneGroupingCriteria,
createGroupingColDefForAllGroupingCriteria,
} from './createGroupingColDef';
import { isDeepEqual } from '../../../utils/utils';
-import { useGridRegisterPreProcessor } from '../../core/preProcessing';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
import { GridColumnRawLookup, GridColumnsRawState } from '../columns/gridColumnsInterfaces';
import { useGridRegisterFilteringMethod } from '../filter/useGridRegisterFilteringMethod';
import { GridFilteringMethod } from '../filter/gridFilterState';
@@ -363,10 +364,7 @@ export const useGridRowGrouping = (
(model) => {
const currentModel = gridRowGroupingModelSelector(apiRef.current.state);
if (currentModel !== model) {
- apiRef.current.setState((state) => ({
- ...state,
- rowGrouping: { ...state.rowGrouping, model },
- }));
+ apiRef.current.setState(mergeStateWithRowGroupingModel(model));
updateRowGrouping();
apiRef.current.forceUpdate();
}
@@ -437,6 +435,48 @@ export const useGridRowGrouping = (
'GridRowGroupingApi',
);
+ /**
+ * PRE-PROCESSING
+ */
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ if (props.disableRowGrouping) {
+ return prevState;
+ }
+
+ const rowGroupingModelToExport = gridRowGroupingModelSelector(apiRef.current.state);
+ if (rowGroupingModelToExport.length === 0) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ rowGrouping: {
+ model: rowGroupingModelToExport,
+ },
+ };
+ },
+ [apiRef, props.disableRowGrouping],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ if (props.disableRowGrouping) {
+ return params;
+ }
+
+ const rowGroupingModel = context.stateToRestore.rowGrouping?.model;
+ if (rowGroupingModel != null) {
+ apiRef.current.setState(mergeStateWithRowGroupingModel(rowGroupingModel));
+ }
+ return params;
+ },
+ [apiRef, props.disableRowGrouping],
+ );
+
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
+
/**
* EVENTS
*/
diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingUtils.ts b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingUtils.ts
index d04a9d4e5b18..767f7eb99456 100644
--- a/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingUtils.ts
+++ b/packages/grid/_modules_/grid/hooks/features/sorting/gridSortingUtils.ts
@@ -9,6 +9,7 @@ import type {
GridSortDirection,
GridSortItem,
GridSortModel,
+ GridState,
} from '../../../models';
type GridSortingFieldComparator = {
@@ -21,6 +22,16 @@ interface GridParsedSortItem {
getSortCellParams: (id: GridRowId) => GridSortCellParams;
}
+export const mergeStateWithSortModel =
+ (sortModel: GridSortModel) =>
+ (state: GridState): GridState => ({
+ ...state,
+ sorting: {
+ ...state.sorting,
+ sortModel,
+ },
+ });
+
const isDesc = (direction: GridSortDirection) => direction === 'desc';
/**
diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts
index 82d01433d05b..b5b9e4e7ddf5 100644
--- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts
+++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts
@@ -20,7 +20,12 @@ import { gridRowIdsSelector, gridRowGroupingNameSelector, gridRowTreeSelector }
import { useGridStateInit } from '../../utils/useGridStateInit';
import { useFirstRender } from '../../utils/useFirstRender';
import { GridSortingMethod, GridSortingMethodCollection } from './gridSortingState';
-import { buildAggregatedSortingApplier, getNextGridSortDirection } from './gridSortingUtils';
+import {
+ buildAggregatedSortingApplier,
+ mergeStateWithSortModel,
+ getNextGridSortDirection,
+} from './gridSortingUtils';
+import { GridPreProcessor, useGridRegisterPreProcessor } from '../../core/preProcessing';
import { useGridRegisterSortingMethod } from './useGridRegisterSortingMethod';
/**
@@ -142,10 +147,7 @@ export const useGridSorting = (
const currentModel = gridSortModelSelector(apiRef.current.state);
if (currentModel !== model) {
logger.debug(`Setting sort model`);
- apiRef.current.setState((state) => ({
- ...state,
- sorting: { ...state.sorting, sortModel: model },
- }));
+ apiRef.current.setState(mergeStateWithSortModel(model));
apiRef.current.forceUpdate();
apiRef.current.applySorting();
}
@@ -210,6 +212,39 @@ export const useGridSorting = (
/**
* PRE-PROCESSING
*/
+ const stateExportPreProcessing = React.useCallback>(
+ (prevState) => {
+ const sortModelToExport = gridSortModelSelector(apiRef.current.state);
+ if (sortModelToExport.length === 0) {
+ return prevState;
+ }
+
+ return {
+ ...prevState,
+ sorting: {
+ sortModel: sortModelToExport,
+ },
+ };
+ },
+ [apiRef],
+ );
+
+ const stateRestorePreProcessing = React.useCallback>(
+ (params, context) => {
+ const sortModel = context.stateToRestore.sorting?.sortModel;
+ if (sortModel == null) {
+ return params;
+ }
+ apiRef.current.setState(mergeStateWithSortModel(sortModel));
+
+ return {
+ ...params,
+ callbacks: [...params.callbacks, apiRef.current.applySorting],
+ };
+ },
+ [apiRef],
+ );
+
const flatSortingMethod = React.useCallback(
(params) => {
if (!params.sortRowList) {
@@ -222,6 +257,8 @@ export const useGridSorting = (
[apiRef],
);
+ useGridRegisterPreProcessor(apiRef, 'exportState', stateExportPreProcessing);
+ useGridRegisterPreProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
useGridRegisterSortingMethod(apiRef, 'none', flatSortingMethod);
/**
diff --git a/packages/grid/_modules_/grid/hooks/features/statePersistence/GridStatePersistenceApi.ts b/packages/grid/_modules_/grid/hooks/features/statePersistence/GridStatePersistenceApi.ts
new file mode 100644
index 000000000000..0edcfdda98ca
--- /dev/null
+++ b/packages/grid/_modules_/grid/hooks/features/statePersistence/GridStatePersistenceApi.ts
@@ -0,0 +1,27 @@
+import { GridInitialState } from '../../../models';
+
+export interface GridStatePersistenceApi {
+ /**
+ * Generates a serializable object containing the exportable parts of the DataGrid state.
+ * These values can then be passed to the `initialState` prop or injected using the `restoreState` method.
+ * @returns {GridInitialState} The exported state.
+ */
+ exportState: () => GridInitialState;
+ /**
+ * Inject the given values into the state of the DataGrid.
+ * @param {GridInitialState} stateToRestore The exported state to restore.
+ */
+ restoreState: (stateToRestore: GridInitialState) => void;
+}
+
+export interface GridRestoreStatePreProcessingValue {
+ /**
+ * Functions to run after the state has been updated but before re-rendering.
+ * This is usually used to apply derived states like `applyFilters` or `applySorting`
+ */
+ callbacks: (() => void)[];
+}
+
+export interface GridRestoreStatePreProcessingContext {
+ stateToRestore: GridInitialState;
+}
diff --git a/packages/grid/_modules_/grid/hooks/features/statePersistence/index.ts b/packages/grid/_modules_/grid/hooks/features/statePersistence/index.ts
new file mode 100644
index 000000000000..799d23a98feb
--- /dev/null
+++ b/packages/grid/_modules_/grid/hooks/features/statePersistence/index.ts
@@ -0,0 +1 @@
+export * from './GridStatePersistenceApi';
diff --git a/packages/grid/_modules_/grid/hooks/features/statePersistence/useGridStatePersistence.ts b/packages/grid/_modules_/grid/hooks/features/statePersistence/useGridStatePersistence.ts
new file mode 100644
index 000000000000..a2b1a6ac5806
--- /dev/null
+++ b/packages/grid/_modules_/grid/hooks/features/statePersistence/useGridStatePersistence.ts
@@ -0,0 +1,40 @@
+import * as React from 'react';
+import { GridApiRef, GridInitialState } from '../../../models';
+import { GridStatePersistenceApi } from './GridStatePersistenceApi';
+import { useGridApiMethod } from '../../utils';
+
+export const useGridStatePersistence = (apiRef: GridApiRef) => {
+ const exportState = React.useCallback(() => {
+ const stateToExport = apiRef.current.unstable_applyPreProcessors('exportState', {});
+
+ return stateToExport as GridInitialState;
+ }, [apiRef]);
+
+ const restoreState = React.useCallback(
+ (stateToRestore) => {
+ const response = apiRef.current.unstable_applyPreProcessors(
+ 'restoreState',
+ {
+ callbacks: [],
+ },
+ {
+ stateToRestore,
+ },
+ );
+
+ response.callbacks.forEach((callback) => {
+ callback();
+ });
+
+ apiRef.current.forceUpdate();
+ },
+ [apiRef],
+ );
+
+ const statePersistenceApi: GridStatePersistenceApi = {
+ exportState,
+ restoreState,
+ };
+
+ useGridApiMethod(apiRef, statePersistenceApi, 'GridStatePersistenceApi');
+};
diff --git a/packages/grid/_modules_/grid/models/api/gridApi.ts b/packages/grid/_modules_/grid/models/api/gridApi.ts
index d6901d82c0b9..9f6866b617fb 100644
--- a/packages/grid/_modules_/grid/models/api/gridApi.ts
+++ b/packages/grid/_modules_/grid/models/api/gridApi.ts
@@ -26,6 +26,7 @@ import type { GridRowGroupsPreProcessingApi } from '../../hooks/core/rowGroupsPe
import type { GridDimensionsApi } from '../../hooks/features/dimensions';
import type { GridRowGroupingApi } from '../../hooks/features/rowGrouping';
import type { GridPaginationApi } from '../../hooks/features/pagination';
+import type { GridStatePersistenceApi } from '../../hooks/features/statePersistence';
/**
* The full grid API.
@@ -58,4 +59,5 @@ export interface GridApi
GridScrollApi,
GridRowGroupingApi,
GridVirtualScrollerApi,
- GridColumnPinningApi {}
+ GridColumnPinningApi,
+ GridStatePersistenceApi {}
diff --git a/packages/grid/x-data-grid-generator/src/useMovieData.ts b/packages/grid/x-data-grid-generator/src/useMovieData.ts
index 9dbcf0ae44b9..068bcc8bfa34 100644
--- a/packages/grid/x-data-grid-generator/src/useMovieData.ts
+++ b/packages/grid/x-data-grid-generator/src/useMovieData.ts
@@ -285,7 +285,7 @@ const ROWS: GridRowModel[] = [
{
id: 20,
title: 'Minions',
- gross: 11159398397,
+ gross: 1159398397,
director: 'Pierre Coffin & Kyle Balda',
company: 'Universal Pictures',
year: 2015,
diff --git a/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
index ff7f606d1e51..b0491af25f5a 100644
--- a/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
+++ b/packages/grid/x-data-grid-pro/src/tests/columnPinning.DataGridPro.test.tsx
@@ -213,7 +213,7 @@ describe(' - Column pinning', () => {
});
describe('props: onPinnedColumnsChange', () => {
- it('shoull call when a column is pinned', () => {
+ it('should call when a column is pinned', () => {
const handlePinnedColumnsChange = spy();
render();
apiRef.current.pinColumn('currencyPair', GridPinnedPosition.left);
@@ -228,7 +228,7 @@ describe(' - Column pinning', () => {
});
});
- it('shoull not change the pinned columns when it is called', () => {
+ it('should not change the pinned columns when it is called', () => {
const handlePinnedColumnsChange = spy();
render(
- Column pinning', () => {
});
describe('props: pinnedColumns', () => {
- it('shoull pin the columns specified', () => {
+ it('should pin the columns specified', () => {
render();
const leftColumns = document.querySelector(
`.${gridClasses['pinnedColumns--left']}`,
@@ -274,7 +274,7 @@ describe(' - Column pinning', () => {
).not.to.equal(null);
});
- it('shoull filter our duplicated columns', () => {
+ it('should filter our duplicated columns', () => {
render();
const leftColumns = document.querySelector(
`.${gridClasses['pinnedColumns--left']}`,
diff --git a/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
new file mode 100644
index 000000000000..3690e5afe211
--- /dev/null
+++ b/packages/grid/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx
@@ -0,0 +1,200 @@
+import * as React from 'react';
+import {
+ DataGridPro,
+ DataGridProProps,
+ GridApiRef,
+ GridInitialState,
+ GridPreferencePanelsValue,
+ useGridApiRef,
+} from '@mui/x-data-grid-pro';
+import { createRenderer, screen } from '@mui/monorepo/test/utils';
+import { useMovieData } from '@mui/x-data-grid-generator';
+import { expect } from 'chai';
+import { getColumnHeadersTextContent, getColumnValues } from '../../../../../test/utils/helperFn';
+
+const isJSDOM = /jsdom/.test(window.navigator.userAgent);
+
+describe(' - State Persistence', () => {
+ const { render, clock } = createRenderer({ clock: 'fake' });
+
+ let apiRef: GridApiRef;
+
+ const TestCase = (props: Omit) => {
+ const data = useMovieData();
+
+ apiRef = useGridApiRef();
+
+ return (
+
+
+
+ );
+ };
+
+ describe('apiRef: exportState', () => {
+ const FULL_INITIAL_STATE: GridInitialState = {
+ columns: {
+ columnVisibilityModel: { year: false },
+ },
+ filter: {
+ filterModel: {
+ items: [{ columnField: 'gross', operatorValue: '>', value: '1500000000' }],
+ },
+ },
+ pagination: {
+ page: 1,
+ pageSize: 2,
+ },
+ pinnedColumns: {
+ left: ['company'],
+ },
+ preferencePanel: {
+ open: true,
+ openedPanelValue: GridPreferencePanelsValue.filters,
+ },
+ sorting: {
+ sortModel: [{ field: 'director', sort: 'asc' }],
+ },
+ rowGrouping: {
+ model: ['director'],
+ },
+ };
+
+ it('should not return the default values of the models', () => {
+ render();
+ expect(apiRef.current.exportState()).to.deep.equal({});
+ });
+
+ it('should export the initial values of the models', () => {
+ render();
+ expect(apiRef.current.exportState()).to.deep.equal(FULL_INITIAL_STATE);
+ });
+
+ it('should export the current version of the exportable state', () => {
+ render();
+ apiRef.current.setPageSize(2);
+ apiRef.current.setPage(1);
+ apiRef.current.setPinnedColumns({ left: ['company'] });
+ apiRef.current.showPreferences(GridPreferencePanelsValue.filters);
+ apiRef.current.setSortModel([{ field: 'director', sort: 'asc' }]);
+ apiRef.current.setFilterModel({
+ items: [{ columnField: 'gross', operatorValue: '>', value: '1500000000' }],
+ });
+ apiRef.current.setRowGroupingModel(['director']);
+ apiRef.current.setColumnVisibilityModel({ year: false });
+
+ expect(apiRef.current.exportState()).to.deep.equal(FULL_INITIAL_STATE);
+ });
+ });
+
+ describe('apiRef: restoreState', () => {
+ it('should restore the whole exportable state', () => {
+ render();
+
+ apiRef.current.restoreState({
+ columns: {
+ columnVisibilityModel: { year: false },
+ },
+ filter: {
+ filterModel: {
+ items: [{ columnField: 'gross', operatorValue: '>', value: '1500000000' }],
+ },
+ },
+ pagination: {
+ page: 1,
+ pageSize: 2,
+ },
+ pinnedColumns: {
+ left: ['company'],
+ },
+ preferencePanel: {
+ open: true,
+ openedPanelValue: GridPreferencePanelsValue.filters,
+ },
+ sorting: {
+ sortModel: [{ field: 'director', sort: 'asc' }],
+ },
+ rowGrouping: {
+ model: ['director'],
+ },
+ });
+
+ // Pagination
+ expect(getColumnValues(0)).to.deep.equal(['', 'Disney Studios', '', 'Universal Pictures']);
+
+ // Sorting and row grouping
+ expect(getColumnValues(1)).to.deep.equal(['J. J. Abrams (1)', '', 'Colin Trevorrow (1)', '']);
+
+ // Filtering
+ expect(getColumnValues(3)).to.deep.equal(['', '2,068,223,624$', '', '1,671,713,208$']);
+
+ // Preference panel
+ expect(screen.getByRole('button', { name: /Add Filter/i })).to.not.equal(null);
+
+ // Columns visibility
+ expect(getColumnHeadersTextContent()).to.not.include('Year');
+
+ // Pinning
+ expect(
+ document.querySelector('.MuiDataGrid-pinnedColumnHeaders--left')?.textContent,
+ ).to.deep.equal('Company');
+ });
+
+ it('should restore partial exportable state', () => {
+ render();
+
+ apiRef.current.restoreState({
+ pagination: {
+ page: 1,
+ pageSize: 2,
+ },
+ });
+
+ expect(getColumnValues(0)).to.deep.equal(['Titanic', 'Star Wars: The Force Awakens']);
+ });
+
+ it('should restore controlled sub-state', () => {
+ const ControlledTest = () => {
+ const [page, setPage] = React.useState(0);
+
+ return (
+ {
+ setPage(newPage);
+ }}
+ />
+ );
+ };
+
+ render();
+ apiRef.current.restoreState({
+ pagination: {
+ page: 1,
+ pageSize: 2,
+ },
+ });
+ clock.runToLast();
+ expect(getColumnValues(0)).to.deep.equal(['Titanic', 'Star Wars: The Force Awakens']);
+ });
+ });
+});
diff --git a/packages/grid/x-data-grid-pro/src/useDataGridProComponent.tsx b/packages/grid/x-data-grid-pro/src/useDataGridProComponent.tsx
index dbe89d47e9b2..f28166cdb981 100644
--- a/packages/grid/x-data-grid-pro/src/useDataGridProComponent.tsx
+++ b/packages/grid/x-data-grid-pro/src/useDataGridProComponent.tsx
@@ -31,6 +31,7 @@ import { useGridDimensions } from '../../_modules_/grid/hooks/features/dimension
import { useGridTreeData } from '../../_modules_/grid/hooks/features/treeData/useGridTreeData';
import { useGridRowGrouping } from '../../_modules_/grid/hooks/features/rowGrouping/useGridRowGrouping';
import { useGridColumnPinning } from '../../_modules_/grid/hooks/features/columnPinning/useGridColumnPinning';
+import { useGridStatePersistence } from '../../_modules_/grid/hooks/features/statePersistence/useGridStatePersistence';
export const useDataGridProComponent = (
inputApiRef: GridApiRef | undefined,
@@ -65,6 +66,7 @@ export const useDataGridProComponent = (
useGridClipboard(apiRef);
useGridDimensions(apiRef, props);
useGridEvents(apiRef, props);
+ useGridStatePersistence(apiRef);
return apiRef;
};
diff --git a/packages/grid/x-data-grid/src/useDataGridComponent.tsx b/packages/grid/x-data-grid/src/useDataGridComponent.tsx
index 570b9f807cdc..e678fd6172fb 100644
--- a/packages/grid/x-data-grid/src/useDataGridComponent.tsx
+++ b/packages/grid/x-data-grid/src/useDataGridComponent.tsx
@@ -24,6 +24,7 @@ import { useGridScroll } from '../../_modules_/grid/hooks/features/scroll/useGri
import { useGridEvents } from '../../_modules_/grid/hooks/features/events/useGridEvents';
import { useGridDimensions } from '../../_modules_/grid/hooks/features/dimensions/useGridDimensions';
import { useGridRowsMeta } from '../../_modules_/grid/hooks/features/rows/useGridRowsMeta';
+import { useGridStatePersistence } from '../../_modules_/grid/hooks/features/statePersistence/useGridStatePersistence';
export const useDataGridComponent = (props: DataGridProcessedProps) => {
const apiRef = useGridInitialization(undefined, props);
@@ -49,6 +50,7 @@ export const useDataGridComponent = (props: DataGridProcessedProps) => {
useGridClipboard(apiRef);
useGridDimensions(apiRef, props);
useGridEvents(apiRef, props);
+ useGridStatePersistence(apiRef);
return apiRef;
};