Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Implement useControlState hook, and add control state on selectionModel #1823

Merged
merged 39 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
45d78ba
Control state hook and api draft
dtassone Jun 8, 2021
fd34487
add more stories and cleanup
dtassone Jun 9, 2021
b11c90f
more progress on control state
dtassone Jun 9, 2021
52ca649
small refactoring
dtassone Jun 10, 2021
9658791
Stabilising control mode
dtassone Jun 11, 2021
4618bca
cleanup
dtassone Jun 11, 2021
ab257ac
more cleanup
dtassone Jun 11, 2021
098afa8
lot of cleanup and small refactoring
dtassone Jun 14, 2021
3fa7da7
cleanup and remove control filter
dtassone Jun 15, 2021
43e288d
cleanup onRowSelected
dtassone Jun 15, 2021
3fdf0a7
fix doc demo
dtassone Jun 15, 2021
85858ff
fix api doc
dtassone Jun 15, 2021
488aa11
code review cleanup
dtassone Jun 16, 2021
bda7c42
remove useCallback of test
dtassone Jun 16, 2021
48f93b6
refactor useGridState to completely abstract control state
dtassone Jun 17, 2021
2232b73
fix docs api
dtassone Jun 17, 2021
6a8fee8
add api doc
dtassone Jun 17, 2021
2ce6337
api page update
dtassone Jun 17, 2021
c0480a4
wip refactoring control state and selection state
dtassone Jun 18, 2021
8e31044
fix hook name
dtassone Jun 21, 2021
89c9b96
Merge branch 'master' of github.com:mui-org/material-ui-x into contro…
dtassone Jun 21, 2021
404f871
fix control selection
dtassone Jun 22, 2021
a0f2253
fix test and control mode
dtassone Jun 22, 2021
6122dec
Merge branch 'master' into controlMode
dtassone Jun 23, 2021
f586d6c
fix event name
dtassone Jun 23, 2021
df5659c
cleanup generic type on registerControlState
dtassone Jun 23, 2021
c6d658c
fix lint
dtassone Jun 23, 2021
bc3c09e
update api docs
dtassone Jun 23, 2021
fd832e5
latest docs api
dtassone Jun 23, 2021
eb745a2
code review
dtassone Jun 30, 2021
68be7b4
fix single selection on dataGrid
dtassone Jun 30, 2021
89a43f9
move test to selection
dtassone Jun 30, 2021
6e57d71
Merge branch 'master' into controlMode
dtassone Jun 30, 2021
12d0e56
fix csv serializer
dtassone Jun 30, 2021
7bbec74
fix docs api
dtassone Jun 30, 2021
3619600
fix test clipboard
dtassone Jun 30, 2021
7bbcc2b
Apply suggestions from code review
dtassone Jul 1, 2021
c0a934e
Merge branch 'master' into controlMode
dtassone Jul 5, 2021
8df9ac9
api and cleanup
dtassone Jul 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/pages/api-docs/data-grid/data-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ import { DataGrid } from '@material-ui/data-grid';
| <span class="prop-name">onRowOut</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse out comes from a row container element. |
| <span class="prop-name">onRowEnter</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse enter comes from a row container element. |
| <span class="prop-name">onRowLeave</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse leave event comes from a row container element. |
| <span class="prop-name">onRowSelected</span> | <span class="prop-type">(param: GridRowSelectedParams) => void</span> | | Callback fired when one row is selected. |
| <span class="prop-name">onSelectionModelChange</span> | <span class="prop-type">(param: GridSelectionModelChangeParams) => void</span> | | Callback fired when the selection state of one or multiple rows changes. |
| <span class="prop-name">onSortModelChange</span> | <span class="prop-type">(param: GridSortModelParams) => void</span> | | Callback fired when the sort model changes before a column is sorted. |
| <span class="prop-name">page</span> | <span class="prop-type">number</span> | 1 | Set the current page. |
Expand Down
2 changes: 2 additions & 0 deletions docs/pages/api-docs/data-grid/grid-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { GridApi } from '@material-ui/x-grid';
| <span class="prop-name">commitCellChange</span> | <span class="prop-type">(params: GridEditCellPropsParams) =&gt; void</span> | Commits a cell change. Used to update the value when editing a cell. |
| <span class="prop-name">components</span> | <span class="prop-type">GridApiRefComponentsProperty</span> | The set of overridable components used in the grid. |
| <span class="prop-name optional">componentsProps<sup><abbr title="optional">?</abbr></sup></span> | <span class="prop-type">GridSlotsComponentsProps</span> | Overrideable components props dynamically passed to the component at rendering. |
| <span class="prop-name">controlStateRef</span> | <span class="prop-type">RefObject&lt;Record&lt;string, ControlStateItem&lt;any, any&gt;&gt;&gt;</span> | |
| <span class="prop-name">deleteFilter</span> | <span class="prop-type">(item: GridFilterItem) =&gt; void</span> | Deletes a GridFilterItem. |
| <span class="prop-name">exportDataAsCsv</span> | <span class="prop-type">(options?: GridExportCsvOptions) =&gt; void</span> | Downloads and exports a CSV of the grid's data. |
| <span class="prop-name">forceUpdate</span> | <span class="prop-type">Dispatch&lt;any&gt;</span> | Forces the grid to rerender. It's often used after a state update. |
Expand Down Expand Up @@ -60,6 +61,7 @@ import { GridApi } from '@material-ui/x-grid';
| <span class="prop-name">isCellEditable</span> | <span class="prop-type">(params: GridCellParams) =&gt; boolean</span> | Controls if a cell is editable. |
| <span class="prop-name">isColumnVisibleInWindow</span> | <span class="prop-type">(colIndex: number) =&gt; boolean</span> | Checks if a column at the index given by `colIndex` is currently visible in the viewport. |
| <span class="prop-name">publishEvent</span> | <span class="prop-type">(name: string, ...args: any[]) =&gt; void</span> | Emits an event. |
| <span class="prop-name">registerControlState</span> | <span class="prop-type">(controlState: ControlStateItem&lt;TModel, TState&gt;) =&gt; void</span> | |
| <span class="prop-name">resize</span> | <span class="prop-type">() =&gt; void</span> | Triggers a resize of the component and recalculation of width and height. |
| <span class="prop-name">scroll</span> | <span class="prop-type">(params: Partial&lt;GridScrollParams&gt;) =&gt; void</span> | Triggers the viewport to scroll to the given positions (in pixels). |
| <span class="prop-name">scrollToIndexes</span> | <span class="prop-type">(params: Optional&lt;GridCellIndexCoordinates, rowIndex&gt;) =&gt; boolean</span> | Triggers the viewport to scroll to the cell at indexes given by `params`.<br />Returns `true` if the grid had to scroll to reach the target. |
Expand Down
1 change: 0 additions & 1 deletion docs/pages/api-docs/data-grid/x-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ import { XGrid } from '@material-ui/x-grid';
| <span class="prop-name">onRowOut</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse out comes from a row container element. |
| <span class="prop-name">onRowEnter</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse enter comes from a row container element. |
| <span class="prop-name">onRowLeave</span> | <span class="prop-type">(param: GridRowParams, event: React.MouseEvent) => void</span> | | Callback fired when a mouse leave event comes from a row container element. |
| <span class="prop-name">onRowSelected</span> | <span class="prop-type">(param: GridRowSelectedParams) => void</span> | | Callback fired when one row is selected. |
| <span class="prop-name">onRowsScrollEnd</span> | <span class="prop-type">(param: GridRowScrollEndParams) => void</span> | | Callback fired when scrolling to the bottom of the grid viewport. |
| <span class="prop-name">onSelectionModelChange</span> | <span class="prop-type">(param: GridSelectionModelChangeParams) => void</span> | | Callback fired when the selection state of one or multiple rows changes. |
| <span class="prop-name">onSortModelChange</span> | <span class="prop-type">(param: GridSortModelParams) => void</span> | | Callback fired when the sort model changes before a column is sorted. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export default function ControlledSelectionGrid() {
<div style={{ height: 400, width: '100%' }}>
<DataGrid
checkboxSelection
onSelectionModelChange={(newSelection) => {
setSelectionModel(newSelection.selectionModel);
onSelectionModelChange={(newSelectionModel) => {
setSelectionModel(newSelectionModel);
}}
selectionModel={selectionModel}
{...data}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export default function ControlledSelectionGrid() {
<div style={{ height: 400, width: '100%' }}>
<DataGrid
checkboxSelection
onSelectionModelChange={(newSelection) => {
setSelectionModel(newSelection.selectionModel);
onSelectionModelChange={(newSelectionModel) => {
setSelectionModel(newSelectionModel);
dtassone marked this conversation as resolved.
Show resolved Hide resolved
}}
selectionModel={selectionModel}
{...data}
Expand Down
6 changes: 0 additions & 6 deletions packages/grid/_modules_/grid/constants/eventsConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,6 @@ export const GRID_ROW_LEAVE = 'rowLeave';
*/
export const GRID_ROW_EDIT_MODEL_CHANGE = 'editRowModelChange';

/**
* Fired when a row is selected or unselected. Called with a [[GridRowSelectedParams]] object.
* @event
*/
export const GRID_ROW_SELECTED = 'rowSelected';
dtassone marked this conversation as resolved.
Show resolved Hide resolved

/**
* Fired when a column header loses focus. Called with a [[GridColumnHeaderParams]] object.
* @ignore - do not document.
Expand Down
28 changes: 14 additions & 14 deletions packages/grid/_modules_/grid/hooks/features/core/gridState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,28 @@ import {
} from '../virtualization/renderingState';

export interface GridState {
rows: InternalGridRowsState;
editRows: GridEditRowsModel;
pagination: GridPaginationState;
options: GridOptions;
isScrolling: boolean;
columns: GridInternalColumns;
columnMenu: GridColumnMenuState;
dtassone marked this conversation as resolved.
Show resolved Hide resolved
columnReorder: GridColumnReorderState;
columnResize: GridColumnResizeState;
columnMenu: GridColumnMenuState;
rendering: InternalRenderingState;
containerSizes: GridContainerProps | null;
viewportSizes: GridViewportSizeState;
density: GridGridDensity;
editRows: GridEditRowsModel;
error?: any;
filter: GridFilterModelState;
focus: GridFocusState;
isScrolling: boolean;
options: GridOptions;
pagination: GridPaginationState;
preferencePanel: GridPreferencePanelState;
rows: InternalGridRowsState;
rendering: InternalRenderingState;
scrollBar: GridScrollBarState;
selection: GridSelectionState;
sorting: GridSortingState;
focus: GridFocusState;
tabIndex: GridTabIndexState;
selection: GridSelectionState;
filter: GridFilterModelState;
viewportSizes: GridViewportSizeState;
visibleRows: VisibleGridRowsState;
preferencePanel: GridPreferencePanelState;
density: GridGridDensity;
error?: any;
}

export const getInitialGridState: () => GridState = () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { GridControlStateApi } from '../../../models/api/gridControlStateApi';
import { ControlStateItem } from '../../../models/controlStateItem';
import { useGridApiMethod } from '../../root/useGridApiMethod';

export function useControlState(apiRef: GridApiRef) {
const controlStateMapRef = React.useRef<Record<string, ControlStateItem<any, any>>>({});
apiRef.current.controlStateRef = controlStateMapRef;

const registerControlState = React.useCallback((controlStateItem: ControlStateItem<any, any>) => {
const { stateId, stateSelector, ...others } = controlStateItem;

controlStateMapRef.current[stateId] = {
...others,
stateId,
stateSelector: !stateSelector ? (state) => state[stateId] : stateSelector,
};
}, []);

const controlStateApi: GridControlStateApi = { registerControlState };
useGridApiMethod(apiRef, controlStateApi, 'controlStateApi');
}
66 changes: 65 additions & 1 deletion packages/grid/_modules_/grid/hooks/features/core/useGridState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,94 @@ import * as React from 'react';
import { GRID_STATE_CHANGE } from '../../../constants/eventsConstants';
import { GridApiRef } from '../../../models/api/gridApiRef';
import { GridStateChangeParams } from '../../../models/params/gridStateChangeParams';
import { isDeepEqual } from '../../../utils/utils';
import { GridState } from './gridState';
import { useGridApi } from './useGridApi';

export const useGridState = (
apiRef: GridApiRef,
): [GridState, (stateUpdaterFn: (oldState: GridState) => GridState) => boolean, () => void] => {
useGridApi(apiRef);

const forceUpdate = React.useCallback(
() => apiRef.current.forceUpdate(() => apiRef.current.state),
[apiRef],
);

const setGridState = React.useCallback(
(stateUpdaterFn: (oldState: GridState) => GridState) => {
const newState = stateUpdaterFn(apiRef.current.state);
const hasChanged = apiRef.current.state !== newState;
let shouldUpdate = true;
dtassone marked this conversation as resolved.
Show resolved Hide resolved
const updatedStateIds: string[] = [];
const controlStateMap = apiRef.current.controlStateRef.current!;

if (hasChanged && apiRef.current.controlStateRef) {
Object.keys(controlStateMap).forEach((stateId) => {
const controlState = controlStateMap[stateId];
const oldState = controlState.stateSelector(apiRef.current.state);
const newSubState = controlState.stateSelector(newState);
const hasSubStateChanged = !isDeepEqual(oldState, newSubState);
dtassone marked this conversation as resolved.
Show resolved Hide resolved

if (updatedStateIds.length >= 1 && hasSubStateChanged) {
// Each hook modify its own state and it should not leak
// Events are here to forward to other hooks and apply changes.
// You are trying to update several states in a no isolated way.
throw new Error(
`You're not allowed to update several sub-state in one transaction. You already updated ${updatedStateIds[0]}, therefore, you're not allowed to update ${controlState.stateId} in the same transaction.`,
);
}

if (hasSubStateChanged) {
if (controlState.propOnChange) {
const newModel = controlState.mapStateToModel
? controlState.mapStateToModel(newSubState)
: newSubState;

if (
(!controlState.mapStateToModel && controlState.propModel !== newModel) ||
(controlState.mapStateToModel && !isDeepEqual(controlState.propModel, newModel))
) {
controlState.propOnChange(newModel);
shouldUpdate = controlState.propModel === undefined;
}
} else if (controlState.propModel !== undefined) {
const oldModel = controlState.mapStateToModel
? controlState.mapStateToModel(oldState)
: oldState;
shouldUpdate = controlState.mapStateToModel
? !isDeepEqual(oldModel, controlState.propModel)
: oldModel !== controlState.propModel;
}
if (shouldUpdate) {
updatedStateIds.push(controlState.stateId);
}
}
});
}
if (!shouldUpdate) {
return false;
}
// We always assign it as we mutate rows for perf reason.
apiRef.current.state = newState;
// TODO deepFreeze(newState);

if (hasChanged && apiRef.current.publishEvent) {
const params: GridStateChangeParams = { api: apiRef.current, state: newState };
apiRef.current.publishEvent(GRID_STATE_CHANGE, params);

updatedStateIds.forEach((stateId) => {
if (controlStateMap[stateId].onChangeCallback) {
const model =
controlStateMap[stateId].mapStateToModel != null
? controlStateMap[stateId].mapStateToModel!(
controlStateMap[stateId].stateSelector(newState),
)
: controlStateMap[stateId].stateSelector(newState);
controlStateMap[stateId].onChangeCallback!(model);
}
});
}

return hasChanged;
},
[apiRef],
Expand Down
Loading