Skip to content

Commit

Permalink
[DataGrid] Avoid inconsistent state export (#5390)
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle authored Jul 21, 2022
1 parent 40b2f7a commit a6bd294
Show file tree
Hide file tree
Showing 30 changed files with 310 additions and 65 deletions.
1 change: 1 addition & 0 deletions docs/data/data-grid/state/RestoreStateApiRef.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export default function RestoreStateApiRef() {
loading={loading}
apiRef={apiRef}
pagination
initialState={{ columns: { columnVisibilityModel: {} } }}
{...data}
/>
</Box>
Expand Down
1 change: 1 addition & 0 deletions docs/data/data-grid/state/RestoreStateApiRef.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ export default function RestoreStateApiRef() {
loading={loading}
apiRef={apiRef}
pagination
initialState={{ columns: { columnVisibilityModel: {} } }}
{...data}
/>
</Box>
Expand Down
1 change: 1 addition & 0 deletions docs/data/data-grid/state/RestoreStateApiRef.tsx.preview
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
loading={loading}
apiRef={apiRef}
pagination
initialState={{ columns: { columnVisibilityModel: {} } }}
{...data}
/>
2 changes: 1 addition & 1 deletion docs/data/data-grid/state/RestoreStateInitialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function RestoreStateInitialState() {

const [savedState, setSavedState] = React.useState({
count: 0,
initialState: undefined,
initialState: data.initialState,
});

const syncState = React.useCallback((newInitialState) => {
Expand Down
4 changes: 2 additions & 2 deletions docs/data/data-grid/state/RestoreStateInitialState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export default function RestoreStateInitialState() {

const [savedState, setSavedState] = React.useState<{
count: number;
initialState: GridInitialState | undefined;
}>({ count: 0, initialState: undefined });
initialState: GridInitialState;
}>({ count: 0, initialState: data.initialState! });

const syncState = React.useCallback((newInitialState: GridInitialState) => {
setSavedState((prev) => ({
Expand Down
1 change: 1 addition & 0 deletions docs/data/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const pages: MuiPage[] = [
{ pathname: '/x/api/data-grid/grid-api', title: 'GridApi' },
{ pathname: '/x/api/data-grid/grid-cell-params', title: 'GridCellParams' },
{ pathname: '/x/api/data-grid/grid-col-def', title: 'GridColDef' },
{ pathname: '/x/api/data-grid/grid-export-state-params', title: 'GridExportStateParams' },
{ pathname: '/x/api/data-grid/grid-filter-form', title: 'GridFilterForm' },
{ pathname: '/x/api/data-grid/grid-filter-item', title: 'GridFilterItem' },
{ pathname: '/x/api/data-grid/grid-filter-model', title: 'GridFilterModel' },
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/x/api/data-grid/grid-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { GridApi } from '@mui/x-data-grid-pro';
| <span class="prop-name">exportDataAsCsv</span> | <span class="prop-type">(options?: GridCsvExportOptions) =&gt; void</span> | Downloads and exports a CSV of the grid's data. |
| <span class="prop-name">exportDataAsExcel [<span class="plan-premium" title="Premium plan"></span>](https://mui.com/store/items/material-ui-premium/)</span> | <span class="prop-type">(options?: GridExcelExportOptions) =&gt; Promise&lt;void&gt;</span> | Downloads and exports an Excel file of the grid's data. |
| <span class="prop-name">exportDataAsPrint</span> | <span class="prop-type">(options?: GridPrintExportOptions) =&gt; void</span> | Print the grid's data. |
| <span class="prop-name">exportState</span> | <span class="prop-type">() =&gt; InitialState</span> | Generates a serializable object containing the exportable parts of the DataGrid state.<br />These values can then be passed to the `initialState` prop or injected using the `restoreState` method. |
| <span class="prop-name">exportState</span> | <span class="prop-type">(params?: GridExportStateParams) =&gt; InitialState</span> | Generates a serializable object containing the exportable parts of the DataGrid state.<br />These values can then be passed to the `initialState` prop or injected using the `restoreState` method. |
| <span class="prop-name">forceUpdate</span> | <span class="prop-type">() =&gt; void</span> | Forces the grid to rerender. It's often used after a state update. |
| <span class="prop-name">getAllColumns</span> | <span class="prop-type">() =&gt; GridStateColDef[]</span> | Returns an array of [GridColDef](/x/api/data-grid/grid-col-def/) containing all the column definitions. |
| <span class="prop-name">getAllRowIds</span> | <span class="prop-type">() =&gt; GridRowId[]</span> | Gets the list of row ids. |
Expand Down
7 changes: 7 additions & 0 deletions docs/pages/x/api/data-grid/grid-export-state-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import MarkdownDocs from '@mui/monorepo/docs/src/modules/components/MarkdownDocs';
import { demos, docs, demoComponents } from './grid-export-state-params.md?@mui/markdown';

export default function Page() {
return <MarkdownDocs demos={demos} docs={docs} demoComponents={demoComponents} />;
}
19 changes: 19 additions & 0 deletions docs/pages/x/api/data-grid/grid-export-state-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# GridExportStateParams Interface

<p class="description"></p>

## Import

```js
import { GridExportStateParams } from '@mui/x-data-grid-premium';
// or
import { GridExportStateParams } from '@mui/x-data-grid-pro';
// or
import { GridExportStateParams } from '@mui/x-data-grid';
```

## Properties

| Name | Type | Default | Description |
| :------------------------------------------------------------------------------------------------------ | :------------------------------------- | :-------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span class="prop-name optional">exportOnlyDirtyModels<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">boolean</span> | <span class="prop-default">false</span> | By default, the grid exports all the models.<br />You can switch this property to `true` to only exports models that are either controlled, initialized or modified.<br />For instance, with this property, if you don't control or initialize the `filterModel` and you did not apply any filter, the model won't be exported.<br />Note that the column dimensions are not a model. The grid only exports the dimensions of the modified columns even when `exportOnlyDirtyModels` is false. |
1 change: 1 addition & 0 deletions docs/scripts/api/buildInterfacesDocumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const OTHER_GRID_INTERFACES_WITH_DEDICATED_PAGES = [
'GridRowParams',
'GridRowClassNameParams',
'GridRowSpacingParams',
'GridExportStateParams',

// Others
'GridColDef',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export const useDataGridPremiumComponent = (
useGridEditing(apiRef, props);

useGridFocus(apiRef, props);
useGridPreferencesPanel(apiRef);
useGridPreferencesPanel(apiRef, props);
useGridFilter(apiRef, props);
useGridSorting(apiRef, props);
useGridDensity(apiRef, props);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const useGridRowGrouping = (
apiRef: React.MutableRefObject<GridApiPremium>,
props: Pick<
DataGridPremiumProcessedProps,
| 'initialState'
| 'rowGroupingModel'
| 'onRowGroupingModelChange'
| 'defaultGroupingExpansionDepth'
Expand Down Expand Up @@ -178,13 +179,20 @@ export const useGridRowGrouping = (
);

const stateExportPreProcessing = React.useCallback<GridPipeProcessor<'exportState'>>(
(prevState) => {
if (props.disableRowGrouping) {
return prevState;
}

(prevState, context) => {
const rowGroupingModelToExport = gridRowGroupingModelSelector(apiRef);
if (rowGroupingModelToExport.length === 0) {

const shouldExportRowGroupingModel =
// Always export if the `exportOnlyDirtyModels` property is activated
!context.exportOnlyDirtyModels ||
// Always export if the model is controlled
props.rowGroupingModel != null ||
// Always export if the model has been initialized
props.initialState?.rowGrouping?.model != null ||
// Export if the model is not empty
Object.keys(rowGroupingModelToExport).length > 0;

if (!shouldExportRowGroupingModel) {
return prevState;
}

Expand All @@ -195,7 +203,7 @@ export const useGridRowGrouping = (
},
};
},
[apiRef, props.disableRowGrouping],
[apiRef, props.rowGroupingModel, props.initialState?.rowGrouping?.model],
);

const stateRestorePreProcessing = React.useCallback<GridPipeProcessor<'restoreState'>>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,81 @@ describe('<DataGridPremium /> - State Persistence', () => {
};

describe('apiRef: exportState', () => {
// We always export the `orderedFields`,
// If it's something problematic we could introduce an `hasBeenReordered` property and only export if at least one column has been reordered.
it('should not return the default values of the models', () => {
it('should export the initial values of the models', () => {
render(<TestCase initialState={FULL_INITIAL_STATE} />);

const exportedState = apiRef.current.exportState();
expect(exportedState.rowGrouping).to.deep.equal(FULL_INITIAL_STATE.rowGrouping);
expect(exportedState.private_aggregation).to.deep.equal(
FULL_INITIAL_STATE.private_aggregation,
);
});

it('should not export the default values of the models when using exportOnlyDirtyModels', () => {
render(<TestCase />);
expect(apiRef.current.exportState()).to.deep.equal({
expect(apiRef.current.exportState({ exportOnlyDirtyModels: true })).to.deep.equal({
columns: {
orderedFields: ['id', 'category'],
},
});
});

it('should export the initial values of the models', () => {
render(<TestCase initialState={FULL_INITIAL_STATE} />);
expect(apiRef.current.exportState()).to.deep.equal(FULL_INITIAL_STATE);
it('should export the current version of the exportable state', () => {
render(<TestCase />);
apiRef.current.setRowGroupingModel(['category']);
apiRef.current.private_setAggregationModel({
id: 'size',
});

const exportedState = apiRef.current.exportState();
expect(exportedState.rowGrouping).to.deep.equal(FULL_INITIAL_STATE.rowGrouping);
expect(exportedState.private_aggregation).to.deep.equal(
FULL_INITIAL_STATE.private_aggregation,
);
});

it('should export the current version of the exportable state', () => {
it('should export the current version of the exportable state when using exportOnlyDirtyModels', () => {
render(<TestCase />);
apiRef.current.setRowGroupingModel(['category']);
apiRef.current.private_setAggregationModel({ id: 'size' });
expect(apiRef.current.exportState()).to.deep.equal(FULL_INITIAL_STATE);
apiRef.current.private_setAggregationModel({
id: 'size',
});

const exportedState = apiRef.current.exportState({ exportOnlyDirtyModels: true });
expect(exportedState.rowGrouping).to.deep.equal(FULL_INITIAL_STATE.rowGrouping);
expect(exportedState.private_aggregation).to.deep.equal(
FULL_INITIAL_STATE.private_aggregation,
);
});

it('should export the controlled values of the models', () => {
render(
<TestCase
rowGroupingModel={FULL_INITIAL_STATE.rowGrouping?.model}
private_aggregationModel={FULL_INITIAL_STATE.private_aggregation?.model}
/>,
);
expect(apiRef.current.exportState().rowGrouping).to.deep.equal(
FULL_INITIAL_STATE.rowGrouping,
);
expect(apiRef.current.exportState().private_aggregation).to.deep.equal(
FULL_INITIAL_STATE.private_aggregation,
);
});

it('should export the controlled values of the models when using exportOnlyDirtyModels', () => {
render(
<TestCase
rowGroupingModel={FULL_INITIAL_STATE.rowGrouping?.model}
private_aggregationModel={FULL_INITIAL_STATE.private_aggregation?.model}
/>,
);
expect(apiRef.current.exportState().rowGrouping).to.deep.equal(
FULL_INITIAL_STATE.rowGrouping,
);
expect(apiRef.current.exportState().private_aggregation).to.deep.equal(
FULL_INITIAL_STATE.private_aggregation,
);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export const useDataGridProComponent = (
useGridEditing(apiRef, props);

useGridFocus(apiRef, props);
useGridPreferencesPanel(apiRef);
useGridPreferencesPanel(apiRef, props);
useGridFilter(apiRef, props);
useGridSorting(apiRef, props);
useGridDensity(apiRef, props);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const useGridColumnPinning = (
apiRef: React.MutableRefObject<GridApiPro>,
props: Pick<
DataGridProProcessedProps,
'disableColumnPinning' | 'pinnedColumns' | 'onPinnedColumnsChange'
'disableColumnPinning' | 'initialState' | 'pinnedColumns' | 'onPinnedColumnsChange'
>,
): void => {
const pinnedColumns = useGridSelector(apiRef, gridPinnedColumnsSelector);
Expand Down Expand Up @@ -213,12 +213,21 @@ export const useGridColumnPinning = (
);

const stateExportPreProcessing = React.useCallback<GridPipeProcessor<'exportState'>>(
(prevState) => {
(prevState, context) => {
const pinnedColumnsToExport = gridPinnedColumnsSelector(apiRef.current.state);
if (
(!pinnedColumnsToExport.left || pinnedColumnsToExport.left.length === 0) &&
(!pinnedColumnsToExport.right || pinnedColumnsToExport.right.length === 0)
) {

const shouldExportPinnedColumns =
// Always export if the `exportOnlyDirtyModels` property is activated
!context.exportOnlyDirtyModels ||
// Always export if the model is controlled
props.pinnedColumns != null ||
// Always export if the model has been initialized
props.initialState?.pinnedColumns != null ||
// Export if the model is not empty
(pinnedColumnsToExport.left ?? []).length > 0 ||
(pinnedColumnsToExport.right ?? []).length > 0;

if (!shouldExportPinnedColumns) {
return prevState;
}

Expand All @@ -227,7 +236,7 @@ export const useGridColumnPinning = (
pinnedColumns: pinnedColumnsToExport,
};
},
[apiRef],
[apiRef, props.pinnedColumns, props.initialState?.pinnedColumns],
);

const stateRestorePreProcessing = React.useCallback<GridPipeProcessor<'restoreState'>>(
Expand Down
Loading

0 comments on commit a6bd294

Please sign in to comment.