Skip to content

Commit

Permalink
implement control state for sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
dtassone committed Jul 7, 2021
1 parent d7df742 commit 2b0a00b
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,8 @@ export default function ServerSortingGrid() {
const [rows, setRows] = React.useState([]);
const [loading, setLoading] = React.useState(false);

const handleSortModelChange = (params) => {
if (params.sortModel !== sortModel) {
setSortModel(params.sortModel);
}
const handleSortModelChange = (newModel) => {
setSortModel(newModel);
};

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
GridRowsProp,
DataGrid,
GridSortModel,
GridSortModelParams,
} from '@material-ui/data-grid';
import { useDemoData, GridData } from '@material-ui/x-grid-data-generator';

Expand Down Expand Up @@ -42,10 +41,8 @@ export default function ServerSortingGrid() {
const [rows, setRows] = React.useState<GridRowsProp>([]);
const [loading, setLoading] = React.useState<boolean>(false);

const handleSortModelChange = (params: GridSortModelParams) => {
if (params.sortModel !== sortModel) {
setSortModel(params.sortModel);
}
const handleSortModelChange = (newModel: GridSortModel) => {
setSortModel(newModel);
};

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import {
GRID_ROWS_UPDATE,
GRID_SORT_MODEL_CHANGE,
} from '../../../constants/eventsConstants';
import { GridComponentProps } from '../../../GridComponentProps';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { GridSortApi } from '../../../models/api/gridSortApi';
import { GridCellValue } from '../../../models/gridCell';
import { GridColDef } from '../../../models/colDef/gridColDef';
import { GridFeatureModeConstant } from '../../../models/gridFeatureMode';
import { GridColumnHeaderParams } from '../../../models/params/gridColumnHeaderParams';
import { GridSortModelParams } from '../../../models/params/gridSortModelParams';
import { GridRowId, GridRowModel, GridRowsProp } from '../../../models/gridRows';
import { GridRowId, GridRowModel } from '../../../models/gridRows';
import {
GridFieldComparatorList,
GridSortItem,
Expand All @@ -26,7 +26,7 @@ import {
import { isDesc, nextGridSortDirection } from '../../../utils/sortingUtils';
import { isEnterKey } from '../../../utils/keyboardUtils';
import { isDeepEqual } from '../../../utils/utils';
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../root/useGridApiEventHandler';
import { useGridApiEventHandler } from '../../root/useGridApiEventHandler';
import { useGridApiMethod } from '../../root/useGridApiMethod';
import { optionsSelector } from '../../utils/optionsSelector';
import { useLogger } from '../../utils/useLogger';
Expand All @@ -36,23 +36,17 @@ import { useGridState } from '../core/useGridState';
import { gridRowCountSelector } from '../rows/gridRowsSelector';
import { sortedGridRowIdsSelector, sortedGridRowsSelector } from './gridSortingSelector';

export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsProp }) => {
export const useGridSorting = (
apiRef: GridApiRef,
props: Pick<GridComponentProps, 'rows' | 'sortModel' | 'onSortModelChange'>,
) => {
const logger = useLogger('useGridSorting');

const [gridState, setGridState, forceUpdate] = useGridState(apiRef);
const options = useGridSelector(apiRef, optionsSelector);
const visibleColumns = useGridSelector(apiRef, visibleGridColumnsSelector);
const rowCount = useGridSelector(apiRef, gridRowCountSelector);

const getSortModelParams = React.useCallback(
(sortModel: GridSortModel): GridSortModelParams => ({
sortModel,
api: apiRef.current,
columns: apiRef.current.getAllColumns(),
}),
[apiRef],
);

const upsertSortModel = React.useCallback(
(field: string, sortItem?: GridSortItem): GridSortModel => {
const existingIdx = gridState.sorting.sortModel.findIndex((c) => c.field === field);
Expand Down Expand Up @@ -211,10 +205,9 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro
if (visibleColumns.length === 0) {
return;
}
apiRef.current.publishEvent(GRID_SORT_MODEL_CHANGE, getSortModelParams(sortModel));
apiRef.current.applySorting();
},
[setGridState, forceUpdate, visibleColumns.length, apiRef, getSortModelParams],
[setGridState, forceUpdate, visibleColumns.length, apiRef],
);

const sortColumn = React.useCallback(
Expand Down Expand Up @@ -300,8 +293,6 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro
useGridApiEventHandler(apiRef, GRID_ROWS_UPDATE, apiRef.current.applySorting);
useGridApiEventHandler(apiRef, GRID_COLUMNS_CHANGE, onColUpdated);

useGridApiOptionHandler(apiRef, GRID_SORT_MODEL_CHANGE, options.onSortModelChange);

const sortApi: GridSortApi = {
getSortModel,
getSortedRows,
Expand All @@ -315,7 +306,7 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro
React.useEffect(() => {
// When the rows prop change, we re apply the sorting.
apiRef.current.applySorting();
}, [apiRef, rows]);
}, [apiRef, props.rows]);

React.useEffect(() => {
if (rowCount > 0) {
Expand All @@ -325,11 +316,24 @@ export const useGridSorting = (apiRef: GridApiRef, { rows }: { rows: GridRowsPro
}, [rowCount, apiRef, logger]);

React.useEffect(() => {
const sortModel = options.sortModel || [];
apiRef.current.updateControlState<GridSortModel>({
stateId: 'sortModel',
propModel: props.sortModel,
propOnChange: props.onSortModelChange,
stateSelector: (state) => state.sorting.sortModel,
onChangeCallback: (model) => {
apiRef.current.publishEvent(GRID_SORT_MODEL_CHANGE, model);
},
});
}, [apiRef, props.sortModel, props.onSortModelChange]);

React.useEffect(() => {
const sortModel = props.sortModel || [];
const oldSortModel = apiRef.current.state.sorting.sortModel;
if (!isDeepEqual(sortModel, oldSortModel)) {
setGridState((state) => ({ ...state, sorting: { ...state.sorting, sortModel } }));
// we use apiRef to avoid watching setSortModel as it will trigger an update on every state change
apiRef.current.setSortModel(sortModel);
apiRef.current.applySorting();
}
}, [options.sortModel, apiRef]);
}, [props.sortModel, apiRef, setGridState]);
};
5 changes: 2 additions & 3 deletions packages/grid/_modules_/grid/models/gridOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { GridColumnHeaderParams } from './params/gridColumnHeaderParams';
import { GridFilterModelParams } from './params/gridFilterModelParams';
import { GridPageChangeParams } from './params/gridPageChangeParams';
import { GridRowParams } from './params/gridRowParams';
import { GridSortModelParams } from './params/gridSortModelParams';
import { GridSelectionModel } from './gridSelectionModel';
import { GridSortDirection, GridSortModel } from './gridSortModel';
import {
Expand Down Expand Up @@ -418,9 +417,9 @@ export interface GridOptions {
onSelectionModelChange?: (selectionModel: GridRowId[]) => void;
/**
* Callback fired when the sort model changes before a column is sorted.
* @param param With all properties from [[GridSortModelParams]].
* @param param With all properties from [[GridSortModel]].
*/
onSortModelChange?: (params: GridSortModelParams) => void;
onSortModelChange?: (model: GridSortModel) => void;
/**
* Callback fired when the state of the grid is updated.
*/
Expand Down
102 changes: 81 additions & 21 deletions packages/grid/x-grid/src/tests/sorting.XGrid.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import * as React from 'react';
import { GridApiRef, GridSortModel, useGridApiRef } from '@material-ui/data-grid';
import {
GridApiRef,
GridComponentProps,
GridSortModel,
useGridApiRef,
} from '@material-ui/data-grid';
import { XGrid } from '@material-ui/x-grid';
import { expect } from 'chai';
import { useFakeTimers } from 'sinon';
import { spy, useFakeTimers } from 'sinon';
import { getColumnValues, getCell, getColumnHeaderCell } from 'test/utils/helperFn';
import {
createClientRenderStrictMode,
Expand Down Expand Up @@ -54,21 +59,16 @@ describe('<XGrid /> - Sorting', () => {

let apiRef: GridApiRef;

const TestCase = (props: {
rows?: any[];
sortModel: GridSortModel;
disableMultipleColumnsSorting?: boolean;
}) => {
const { sortModel, rows, disableMultipleColumnsSorting } = props;
const TestCase = (props: Partial<GridComponentProps>) => {
const { rows, ...other } = props;
apiRef = useGridApiRef();
return (
<div style={{ width: 300, height: 300 }}>
<XGrid
apiRef={apiRef}
{...baselineProps}
rows={rows || baselineProps.rows}
sortModel={sortModel}
disableMultipleColumnsSorting={disableMultipleColumnsSorting}
rows={props.rows || baselineProps.rows}
{...other}
/>
</div>
);
Expand Down Expand Up @@ -115,7 +115,8 @@ describe('<XGrid /> - Sorting', () => {
});

it('should allow apiRef to setSortModel', () => {
renderBrandSortedAsc();
render(<TestCase />);

apiRef.current.setSortModel([{ field: 'brand', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
});
Expand All @@ -130,8 +131,8 @@ describe('<XGrid /> - Sorting', () => {
});

it('should allow to set multiple Sort items via apiRef', () => {
renderBrandSortedAsc();
expect(getColumnValues()).to.deep.equal(['Adidas', 'Nike', 'Puma']);
render(<TestCase />);

const sortModel: GridSortModel = [
{ field: 'year', sort: 'desc' },
{ field: 'brand', sort: 'asc' },
Expand All @@ -144,7 +145,8 @@ describe('<XGrid /> - Sorting', () => {
describe('multi-sorting', () => {
['shiftKey', 'metaKey', 'ctrlKey'].forEach((key) => {
it(`should do a multi-sorting when clicking the header cell while ${key} is pressed`, () => {
render(<TestCase sortModel={[{ field: 'year', sort: 'desc' }]} />);
render(<TestCase />);
apiRef.current.setSortModel([{ field: 'year', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
fireEvent.click(getColumnHeaderCell(0), { [key]: true });
expect(getColumnValues()).to.deep.equal(['Puma', 'Adidas', 'Nike']);
Expand All @@ -153,7 +155,8 @@ describe('<XGrid /> - Sorting', () => {

['metaKey', 'ctrlKey'].forEach((key) => {
it(`should do nothing when pressing Enter while ${key} is pressed`, () => {
render(<TestCase sortModel={[{ field: 'year', sort: 'desc' }]} />);
render(<TestCase />);
apiRef.current.setSortModel([{ field: 'year', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
getColumnHeaderCell(1).focus();
fireEvent.keyDown(getColumnHeaderCell(1), { key: 'Enter', [key]: true });
Expand All @@ -162,24 +165,25 @@ describe('<XGrid /> - Sorting', () => {
});

it('should do a multi-sorting pressing Enter while shiftKey is pressed', () => {
render(<TestCase sortModel={[{ field: 'year', sort: 'desc' }]} />);
render(<TestCase />);
apiRef.current.setSortModel([{ field: 'year', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
getColumnHeaderCell(0).focus();
fireEvent.keyDown(getColumnHeaderCell(0), { key: 'Enter', shiftKey: true });
expect(getColumnValues()).to.deep.equal(['Puma', 'Adidas', 'Nike']);
});

it(`should not do a multi-sorting if no multiple key is pressed`, () => {
render(<TestCase sortModel={[{ field: 'year', sort: 'desc' }]} />);
render(<TestCase />);
apiRef.current.setSortModel([{ field: 'year', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
fireEvent.click(getColumnHeaderCell(0));
expect(getColumnValues()).to.deep.equal(['Adidas', 'Nike', 'Puma']);
});

it('should not do a multi-sorting if disableMultipleColumnsSorting is true', () => {
render(
<TestCase sortModel={[{ field: 'year', sort: 'desc' }]} disableMultipleColumnsSorting />,
);
render(<TestCase disableMultipleColumnsSorting />);
apiRef.current.setSortModel([{ field: 'year', sort: 'desc' }]);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
fireEvent.click(getColumnHeaderCell(0), { shiftKey: true });
expect(getColumnValues()).to.deep.equal(['Adidas', 'Nike', 'Puma']);
Expand Down Expand Up @@ -264,4 +268,60 @@ describe('<XGrid /> - Sorting', () => {
setProps({ extra: true });
expect(renderCellCount).to.equal(2);
});

describe('control Sorting', () => {
it('should update the sorting state when neither the model nor the onChange are set', () => {
render(<TestCase />);
expect(getColumnValues()).to.deep.equal(['Nike', 'Adidas', 'Puma']);
fireEvent.click(getColumnHeaderCell(0));
expect(getColumnValues()).to.deep.equal(['Adidas', 'Nike', 'Puma']);
});

it('should not update the sort model when the sortModelProp is set', () => {
const testSortModel: GridSortModel = [{ field: 'brand', sort: 'desc' }];
render(<TestCase sortModel={testSortModel} />);
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
fireEvent.click(getColumnHeaderCell(0));
expect(getColumnValues()).to.deep.equal(['Puma', 'Nike', 'Adidas']);
});

it('should update the sort state when the model is not set, but the onChange is set', () => {
const onModelChange = spy();
render(<TestCase onSortModelChange={onModelChange} />);
expect(onModelChange.callCount).to.equal(0);
fireEvent.click(getColumnHeaderCell(0));
expect(onModelChange.callCount).to.equal(1);
expect(onModelChange.lastCall.firstArg).to.deep.equal([{ field: 'brand', sort: 'asc' }]);
});

it('should control sort state when the model and the onChange are set', () => {
let expectedModel: GridSortModel = [];
const ControlCase = (props: Partial<GridComponentProps>) => {
const { rows, columns, ...others } = props;
const [caseSortModel, setSortModel] = React.useState<GridSortModel>([]);
const handleSortChange = (newModel) => {
setSortModel(newModel);
expectedModel = newModel;
};

return (
<div style={{ width: 300, height: 300 }}>
<XGrid
autoHeight={isJSDOM}
columns={columns || baselineProps.columns}
rows={rows || baselineProps.rows}
sortModel={caseSortModel}
onSortModelChange={handleSortChange}
{...others}
/>
</div>
);
};

render(<ControlCase />);
fireEvent.click(getColumnHeaderCell(0));
expect(getColumnValues()).to.deep.equal(['Adidas', 'Nike', 'Puma']);
expect(expectedModel).to.deep.equal([{ field: 'brand', sort: 'asc' }]);
});
});
});
Loading

0 comments on commit 2b0a00b

Please sign in to comment.