-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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] Persist selection from selectionModel
when page changes
#1864
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,10 @@ export const useGridSelection = (apiRef: GridApiRef): void => { | |
logger.debug(`Selecting row ${id}`); | ||
|
||
setGridState((state) => { | ||
if (selectionModel) { | ||
return state; | ||
} | ||
|
||
let selectionState: GridSelectionState = { ...state.selection }; | ||
const allowMultiSelect = | ||
allowMultipleOverride || | ||
|
@@ -94,8 +98,16 @@ export const useGridSelection = (apiRef: GridApiRef): void => { | |
data: row, | ||
isSelected: selectionState[id] !== undefined, | ||
}; | ||
|
||
let newSelectionModel = Object.values(selectionState); | ||
if (selectionModel) { | ||
newSelectionModel = selectionModel.includes(rowModelParams.id) | ||
? selectionModel.filter((selection) => selection !== rowModelParams.id) | ||
: [...selectionModel, rowModelParams.id]; | ||
} | ||
Comment on lines
+102
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, are we implementing the logic that is inside the above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it looks like it but it is much simpler. That one only appends the newly selected row id to the already existing selection model so that the value of the params that the developer will get from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Being simpler is suspicious. What about multi-selection? Does it still work correctly? I went on trying it out. The answer is NO, it breaks it: https://codesandbox.io/s/material-demo-forked-e0puo?file=/demo.js. It's the same root problem as https://github.com/mui-org/material-ui-x/pull/1864/files#r652274738.
It seems that we need the exact same output as in One thing that is counterintuitive to me is why we don't have the exact same structure between the controlled prop and the state we store. If the why is performance, caching, then this value stored in the state should be derived from the source of truth (the controlled prop structure), not become the source of truth as it's now. cc @dtassone what do you think? I'm asking because it resonates with the ongoing effort to structure more controlled implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use an object for the selection state to avoid looping through an array to know if a row is selected, otherwise, if you have 1000k rows we would have a perf hit. However, for the prop, using an object is an option but it seemed to make less sense, and it is more convenient to use an array. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dtassone The caching makes sense, it definitely seems valid. What do you think about we stored the controlled prop as is, in the store, and store a private cache, a derivated value, in another part of the state (one that we wouldn't expose to the developers that want to store the state in their database ). But more importantly, have updates on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well the controlled prop to me is already stored and is the value of the prop itself. Why would we need to duplicate the values? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To fix the very first comment of this thread.
The controlled prop There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I was looking in that use case yesterday and what I struggle to understand is how would you differentiate multiselection using Then for the muti-selection and checkbox selection, you would append/remove IDs based on what is being selected/deselected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considering #1823 and the equality issue and the discussion here, how about we transform state to be optimized in the selector. As they are memoized it will only do the transformation when the underlying model change. That way it would allow us to store the model in the state, fix the equality issue #1823 and avoid model duplication. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@dtassone This could work if we also expose the raw state. We need the raw state to perform the control feedback loop.
@DanailH I'm not following. Is "you" a maintainer or a developer? But in any case, I don't understand the problem. Developers don't need to differentiate multiselection, they get the whole state. And as a maintainer, we have access to |
||
|
||
const selectionChangeParam: GridSelectionModelChangeParams = { | ||
selectionModel: Object.values(selectionState), | ||
selectionModel: newSelectionModel, | ||
}; | ||
apiRef.current.publishEvent(GRID_ROW_SELECTED, rowSelectedParam); | ||
apiRef.current.publishEvent(GRID_SELECTION_CHANGED, selectionChangeParam); | ||
|
@@ -106,6 +118,7 @@ export const useGridSelection = (apiRef: GridApiRef): void => { | |
apiRef, | ||
logger, | ||
checkboxSelection, | ||
selectionModel, | ||
forceUpdate, | ||
setGridState, | ||
], | ||
|
@@ -198,10 +211,14 @@ export const useGridSelection = (apiRef: GridApiRef): void => { | |
|
||
React.useEffect(() => { | ||
setGridState((state) => { | ||
if (selectionModel) { | ||
return state; | ||
} | ||
Comment on lines
+214
to
+216
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The two changes in this useEffect looks KO. The behavior should be controlled invariant. Meaning, if the uncontrolled selection unselects when the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, we stop unselecting if controlled 💥 |
||
|
||
const newSelectionState = { ...state.selection }; | ||
DanailH marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let hasChanged = false; | ||
Object.keys(newSelectionState).forEach((id: GridRowId) => { | ||
if (!rowsLookup[id]) { | ||
if (!rowsLookup[id] || !selectionModel) { | ||
delete newSelectionState[id]; | ||
hasChanged = true; | ||
} | ||
|
@@ -212,7 +229,7 @@ export const useGridSelection = (apiRef: GridApiRef): void => { | |
return state; | ||
}); | ||
forceUpdate(); | ||
}, [rowsLookup, apiRef, setGridState, forceUpdate]); | ||
}, [rowsLookup, selectionModel, apiRef, setGridState, forceUpdate]); | ||
|
||
React.useEffect(() => { | ||
const currentModel = Object.values(apiRef.current.getState().selection); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should not use the
options.selectionModel
here as the grid read the state. So the state is the source of truth and should reflect what is in option.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the
selectionModel
is not stored in the state right? From what I understood if theselectionModel
is provided then that should be the one that is used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes so if you set the model it updates the
state.selection
, then if you select a row, the state will be updated. Therefore, the source of truth isstate.selection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't access the options inside any of the selectors. I would check if it's in controlled mode in the hook.
Yeah, I was expecting the same. But instead of accessing the prop directly I would make the state to reflect the value of the prop, that means not updating the state directly when the selection changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can understand why we would want to keep the selector state only, we have never used the options before, it can feel scary, a new pattern. 👍 for trying a solution without.
For the prop updating the state or not. In https://github.com/mui-org/material-ui/blob/2c4dc994da164a53b253f03bcd2425a52655f175/packages/material-ui-utils/src/useControlled.js#L8 we skip the state. However, here, we have a double notion of prop controlled and state-controlled, with the state with more priority. So we probably want to update the state (as done in the pagination).