Closed lisaWriteJava closed 1 year ago
there isn't anything that shows this being able to be automatic once the user sorts/filters, etc
Could you elaborate more a bit the requirement? You want to save a state snapshot on every sort or filter operation? Is the rationale to revert to the current state if the user reopens/refreshes the page?
I believe you can achieve that by:
onFilterModelChange
, onSortModelChange
)localStorage
, db
, etc.initialState
or apiRef.current.restoreState
on page load (or whenever you want)About supporting it out of the box: I am not in favor of it:
Let me know if the suggested method would work in your use-case.
Thanks
So I was trying to figure out a way that if a user sorts the columns, and/or reorders the columns, or pagination we can save that state without the need to create that view. Also, I want the state to be maintained if the user clicks goes to another page or refreshes. Essentially I'd like to persist the state of the datagrid.
how can I do this.
I've prepared a demo implementation that saves the exported state on every state change to localStorage and reloads it on page reload. Have a look at it: https://codesandbox.io/s/datagridpremium-basic-forked-5542fr?file=/src/App.tsx
Design details:
onStateChange
event handler which fires a lot of times based on different internal state changes, it may cause performance issues, especially if you decide to switch to API for storing the state, I'd recommend doing it in a more "controlled" way by limiting the state export to specific event handlers you are interested in, like onFilterModelChange
for example.apiRef.current.exportState
and accepted by initialState
prop.Let me know if the demo helps you achieve your required use case.
Thanks
I think this use case (persist and recover state locally) could be a good candidate for a recipe and could be useful for certain desktop-oriented apps. @cherniavskii Do you think it's worth it to add to the Recipes umbrella issue?
thank you so much. I gave it a quick try and it's doing what I'd like to achieve. I'll let the powers that be know about the caveat no. 3. Honestly I think it would be great to add to the recipies. I went looking outside of MUI for this behavior and I saw something sort of like this but they couldn't get it to work with as many states and so I had to open a ticket. This is beyond helpful. Thank you so much.
Just one question, if I have an initial state set for a grid ie sort order is it possible to have both with this scenario
Just one question, if I have an initial state set for a grid ie sort order is it possible to have both with this scenario
Sure thing, feel free to update the initialState
populating logic as per your requirement!
Just to confirm something like below is fine:
initialState={{
...cachedInitialState,
sorting: {
sortModel: sortModel
}
}}
Just to confirm something like below is fine:
Sure, you can override the sortModel
if the intention is to never persist it and always use a specific initialValue
for it.
But if the intention is to do this only for the first time and let the user change the sortModel afterwards, you can return this as a default value from getStateSnapshotFromLocalStorage
method instead of {}
ahh ok great. thanks for everything this was a big help
Hello,
While your code standbox worked for me, When I incorporated it into my code the state didn't save. I'm workign up the sandbox
ok here's an example of the state not saving when implemented in my code
https://codesandbox.io/s/datagridpremium-basic-forked-pk9s6y
The codesandbox link you shared appears to be broken, here's the error it's throwing
This is the updated sandbox: https://codesandbox.io/s/datagridpremium-basic-forked-pk9s6y?file=/src/CommonGrid.jsx
saving the sandbox was a challenge just in case the first link above doesn't work I also forked it to save here https://codesandbox.io/s/datagridpremium-basic-forked-6zpflz
@MBilalShafi
Just in case there is an issue viewing the sand box @MBilalShafi here is the code:
import { Box } from "@mui/material";
import CircularProgress from "@mui/material/CircularProgress";
import {
DataGridPremium,
GridToolbarContainer,
GridToolbarColumnsButton,
GridToolbarFilterButton,
GridToolbarExport,
GridToolbarDensitySelector,
gridClasses,
useGridApiRef,
GRID_DETAIL_PANEL_TOGGLE_COL_DEF
} from "@mui/x-data-grid-premium";
import PropTypes from "prop-types";
import { alpha } from "@mui/material/styles";
import { styled } from "@mui/material/styles";
const ODD_OPACITY = 0.2;
//class actions
function saveStateSnapshotToLocalStorage(stateSnapshot) {
console.info(
"State update: Update the state snap in localStorage",
stateSnapshot
);
localStorage.setItem("dataGridState", JSON.stringify(stateSnapshot));
}
function getStateSnapshotFromLocalStorage() {
const stringifiedState = localStorage.getItem("dataGridState");
return new Promise((resolve) => {
if (!stringifiedState) {
return resolve({});
}
const stateSnapshot = JSON.parse(stringifiedState);
return resolve(stateSnapshot);
});
}
function CustomToolbar() {
return (
<GridToolbarContainer
sx={{
width: "100%",
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}}
>
<GridToolbarColumnsButton
sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}}
/>
<GridToolbarFilterButton
sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}}
/>
<GridToolbarDensitySelector
sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}}
/>
<GridToolbarExport
sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}}
/>
</GridToolbarContainer>
);
}
const StyledGridOverlay = styled("div")(({ theme }) => ({
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100%",
"& .ant-empty-img-1": {
fill: "#262626"
},
"& .ant-empty-img-2": {
fill: "#595959"
},
"& .ant-empty-img-3": {
fill: "#434343"
},
"& .ant-empty-img-4": {
fill: "#1c1c1c"
},
"& .ant-empty-img-5": {
fillOpacity: "0.08",
fill: "#fff"
}
}));
function CustomNoRowsOverlay() {
return (
<StyledGridOverlay>
<svg
width="120"
height="100"
viewBox="0 0 184 152"
aria-hidden
focusable="false"
>
<g fill="none" fillRule="evenodd">
<g transform="translate(24 31.67)">
<ellipse
className="ant-empty-img-5"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
/>
<path
className="ant-empty-img-1"
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
/>
<path
className="ant-empty-img-2"
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
/>
<path
className="ant-empty-img-3"
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
/>
</g>
<path
className="ant-empty-img-3"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
/>
<g className="ant-empty-img-4" transform="translate(149.65 15.383)">
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815" />
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z" />
</g>
</g>
</svg>
<Box sx={{ mt: 1, fontSize: 16, fontWeight: "bolder" }}>No Data</Box>
</StyledGridOverlay>
);
}
const mockViolationColumns = [
{
id: "user",
headerName: "User",
field: "user",
type: "string",
flex: 1,
aggregable: false,
groupable: false,
headerAlign: "center",
valueGetter: (params) => {
// console.log('in value getting for User params: ', params)
//Asided from everything else, must set somethign in maybe name or a field to do the filter on
return params.value.name;
},
renderCell: (rowInfo) => {
// console.log('valueFormatter for User: ', rowInfo) //you need to verify values below
if (rowInfo.row.user.name) {
return <div> placeHolder </div>;
} else {
return <span>Missing Name Info 2</span>;
}
}
},
{
id: "violation_type",
headerName: "Violation Type",
field: "is_role",
flex: 1,
headerAlign: "center",
aggregable: false,
groupable: false,
//Change to value gettter
renderCell: (rowInfo) => {
return rowInfo.value;
},
valueGetter: (params) => {
// console.log('The violation type value getter ', params, ' \n params.row.is_role: ', params.row.is_role, ' trying value too: ', params.value)
if (params.row.is_role) {
return "Unqualified User (Role)";
} else {
return "Unassigned User (Accessor)";
}
}
},
{
field: "accessor_or_role",
headerName: "Accessor or Role",
flex: 1,
headerAlign: "center",
aggregable: false,
groupable: false,
//This allows for rendering a react component
renderCell: (rowInfo) => {
// console.log(' Ist accessor or role: ', rowInfo)
if (rowInfo.row.is_role) {
return <div> placeHolder </div>;
}
return <div> placeHolder </div>;
},
valueGetter: (params) => {
// console.log('The violation type value getter ', params, ' \n params.row.is_role: ', params.row.is_role, ' trying value too: ', params.value)
if (params.row.is_role) {
return params.row.role.name;
} else {
return params.row.accessor.name;
}
}
},
{
id: "detected_on",
headerName: "Detected On",
field: "created_date",
flex: 1,
type: "date",
headerAlign: "center",
aggregable: false,
groupable: false,
valueFormatter: (params) => {
//console.log('VF The created_date on params: ', params)
if (params.value == null) {
return "";
}
return params.value;
},
valueGetter: (params) => {
// console.log('VG the created_date in params: ', params)
if (params.row.created_date == null) {
return "";
}
// console.log('created_date The last modified from row: ', params.row.created_date)
const date = new Date(params.row.created_date);
// console.log('to return the created_date in getter: ', date)
return date;
}
},
{
id: "last_updated",
headerName: "Last Updated",
field: "last_modified",
flex: 1,
type: "date",
headerAlign: "center",
aggregable: false,
groupable: false,
valueFormatter: (params) => {
// console.log('VF The last_modified on params: ', params)
if (params.value == null) {
return "";
}
return params.value;
},
valueGetter: (params) => {
// console.log('VG the last_modified in params: ', params)
if (params.row.last_modified == null) {
return "";
}
//console.log('last_modified The last modified from row: ', params.row.last_modified)
const date = new Date(params.row.last_modified);
//console.log('to return the last_modified in getter: ', date)
return date;
}
},
{
headerName: "Suspension",
type: "string",
field: "rolesuspension",
flex: 1,
aggregable: false,
groupable: false,
headerAlign: "center",
renderCell: (rowInfo) => {
// console.log('Row info rolesuspension to view: ', rowInfo)
if (!rowInfo.row.is_role) {
return (
<span>
<i>N/A</i>
</span>
);
}
const roleSus = rowInfo.row.rolesuspension;
//console.log(rowInfo, ' = the rolesuspension ', roleSus)
if (!roleSus || roleSus.cleared_date) {
return <span>None</span>;
}
if (roleSus.suspension_activated_date) {
return (
<span>
<span
className="fa fa-minus-circle text-danger"
role="img"
aria-hidden={true}
/>{" "}
Suspended {roleSus.suspension_activated_date}
</span>
);
}
const daysRemaining = 4;
return (
<span>
<span
className="fa fa-hourglass-half"
role="img"
aria-hidden={true}
/>{" "}
{daysRemaining} day{daysRemaining != 1 && "s"} until suspension
</span>
);
},
valueGetter: (rowInfo) => {
// console.log('Row info rolesuspension: ', rowInfo)
if (!rowInfo.row.is_role) {
//may need to use the is_role property This needs to be checked HS and maybe a more examples ffor this poroperty
return "N/A";
}
const roleSus = rowInfo.value;
if (!roleSus || roleSus.cleared_date) {
return "None";
}
if (roleSus.suspension_activated_date) {
return "Suspended" + roleSus.suspension_activated_date;
}
const daysRemaining = 4;
let daysVerbiage = "";
if (daysRemaining > 1) {
daysVerbiage = daysRemaining + " days until suspension";
} else {
daysVerbiage = daysRemaining + " day until suspension";
}
return daysVerbiage;
}
},
{
...GRID_DETAIL_PANEL_TOGGLE_COL_DEF,
renderHeader: () => "+/-",
headerAlign: "center",
sortable: false,
filterable: false,
aggregable: false,
groupable: false,
valueGetter: (params) => {
// console.log('VG the GRID_DETAIL_PANEL_TOGGLE_COL_DEF in params: ', params)
// console.log('return the GRID_DETAIL_PANEL_TOGGLE_COL_DEF in params: ', params.row.id + "-" + params.row.name)
return params.row.id + "-" + params.row.url;
},
renderCell: (params) => {
// console.log('renderCell the GRID_DETAIL_PANEL_TOGGLE_COL_DEF in params: ', params)
return <div>test</div>;
}
}
];
const mockUserMembershipViolations = {
data: {
violations: [
{
id: 1,
uuid: "123",
user: {
sid: "1234",
name: "Apple Cider 2",
first_name: "Apple",
last_name: "Cider 2",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April is A ROLE",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "An accessor Name",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: true,
created_date: "2023-02-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-03-11T14:51:39Z",
rolesuspension: null
},
{
id: 2,
uuid: "123ab",
user: {
sid: "1234",
name: "Samy Zebra",
first_name: "Samy",
last_name: "Zebra",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April Role",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "Not a Role Accessor Name BOO",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: false,
created_date: "2023-01-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-05-11T14:51:39Z",
rolesuspension: null
}
],
users: [
{
sid: "1234",
name: "geger",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
{
sid: "1tt234",
name: "2geger",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
}
],
roles: [
{
name: "April",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
}
],
accessors: [
{
sid: "1234",
name: "geger",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "accessor",
entity_types: []
}
]
},
isFetching: false,
fetched: false,
hasError: true,
errorObj: {}
};
const CommonGrid = ({ ...other }) => {
const apiRef = useGridApiRef();
const [cachedInitialState, setCachedInitialState] = React.useState();
const [sortModel, setSortModel] = React.useState([
{
field: "user",
sort: "desc"
},
{ field: "detected_on", desc: "desc" }
]);
React.useEffect(() => {
const getSnapshot = async () => {
const snapshotFromLocalStorage = await getStateSnapshotFromLocalStorage();
console.log(
"Page Mount: Recovered state from localStorage",
snapshotFromLocalStorage
);
setCachedInitialState(snapshotFromLocalStorage);
};
getSnapshot();
}, []);
const saveSnapshot = React.useCallback(() => {
const stateSnapshot = apiRef.current.exportState();
saveStateSnapshotToLocalStorage(stateSnapshot);
}, [apiRef]);
if (!cachedInitialState) {
return <CircularProgress />;
}
return (
<Box
sx={{
height: "100%",
width: "100%",
"& .datagridHeader": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiDataGrid-footerContainer": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiTablePagination-root": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiTablePagination-displayedRows": {
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiSelect-standard": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important"
},
"& .MuiTablePagination-actions": {
fontSize: 13,
fontWeight: "bolder",
color: "#fff !important",
"& .MuiButtonBase-root": {
color: "#fff !important",
fontSize: 13
}
},
"& .MuiTablePagination-selectLabel": {
fontSize: 13,
fontWeight: "bolder",
color: "#fff !important"
},
"& .MuiTablePagination-selectIcon": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important"
},
"& .MuiTablePagination-selectRoot": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important"
},
"& .MuiTablePagination-input": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important"
},
"& .MuiDataGrid-columnHeader": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700
},
"& .MuiDataGrid-columnHeaderCheckbox": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700
},
"& .MuiDataGrid-cell": {
border: "1px solid rgb(224, 224, 224)",
fontSize: 13
},
"& .MuiDataGrid-root .MuiDataGrid-cell": {
whiteSpace: "normal !important",
wordWwrap: "break-word !important",
textAlign: "left"
},
"& .MuiDataGrid-sortIcon": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700,
opacity: "inherit !important"
},
"& .MuiDataGrid-filterIcon": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700,
opacity: "inherit !important"
},
"& .MuiDataGrid-columnHeaderTitle": {
fontWeight: "bolder"
}
}}
>
{"Tes"}
<br />
<br />
<DataGridPremium
apiRef={apiRef}
onStateChange={saveSnapshot}
rows={[
{
id: 1,
uuid: "123",
user: {
sid: "1234",
name: "Apple Cider 2",
first_name: "Apple",
last_name: "Cider 2",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April is A ROLE",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "An accessor Name",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: true,
created_date: "2023-02-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-03-11T14:51:39Z",
rolesuspension: null
},
{
id: 2,
uuid: "123ab",
user: {
sid: "1234",
name: "Samy Zebra",
first_name: "Samy",
last_name: "Zebra",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April Role",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "Not a Role Accessor Name BOO",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: false,
created_date: "2023-01-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-05-11T14:51:39Z",
rolesuspension: null
}
]}
columns={mockViolationColumns}
sx={{
width: "100% !important",
".MuiDataGrid-iconButtonContainer": {
visibility: "visible"
},
".MuiDataGrid-sortIcon": {
opacity: "inherit !important"
},
".MuiTouchRipple-root": {
opacity: "inherit !important",
visibility: "visible !important"
},
".MuiDataGrid-menuIconButton": {
color: "#fff"
},
".MuiDataGrid-columnHeaderTitleContainerContent .MuiCheckbox-root": {
color: "#ffff !important",
fontSize: 14,
fontWeight: "bolder"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar": {
width: "0.4em"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track": {
color: "#4d7496"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb": {
backgroundColor: "#4d7496",
color: "#fff"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover": {
backgroundColor: "#4d7496",
color: "#fff"
},
"& .MuiDataGrid-iconButtonContainer": {
marginLeft: "25px",
visibility: "visible !important",
width: "auto !important"
},
"& .MuiDataGrid-cell--withRenderer": {
fontSize: "13px !important"
},
"& .MuiDataGrid-menuIcon": {
fontSize: 14,
visibility: "visible !important",
width: "auto !important",
fontWeight: "bolder",
"& .MuiSvgIcon-root": {
fontSize: 14,
visibility: "visible !important",
width: "auto !important",
fontWeight: "bolder"
}
}
}}
getDetailPanelContent={other.getDetailPanelContent}
getDetailPanelHeight={() => "auto"}
rowThreshold={other.rowThreshold}
keepColumnPositionIfDraggedOutside={true}
loading={false}
paginationMode={"client"}
slots={{
noRowsOverlay: CustomNoRowsOverlay,
toolbar: CustomToolbar
}}
checkboxSelection
experimentalFeatures={{
ariaV7: true,
newEditingApi: true
}}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd"
}
initialState={{
cachedInitialState,
sorting: {
sortModel: sortModel
}
}}
sortingOrder={["desc", "asc"]}
sortModel={sortModel}
onSortModelChange={(model) => {
// console.log('****************** The column sort order chagnes \n\n\n\n')
setSortModel(model); //set the state
}}
pagination
/>
</Box>
);
};
/* CommonGrid.propTypes = {
columns: PropTypes.array,
rows: PropTypes.array,
tableTitle: PropTypes.string
}; */
export default CommonGrid;
Hello @romgrk , please let me know what you need from me. I gave the code block above just in case the sandbox didn't load properly to show the issue.
Hi @lisaWriteJava I believe there's a mistake in the initial state merging:
initialState={{
- cachedInitialState,
+ ...cachedInitialState,
sorting: {
sortModel: sortModel
}
}}
Here's a fixed demo: https://codesandbox.io/s/datagridpremium-basic-forked-w25xxc?file=/src/CommonGrid.jsx
This is a great candidate for the recipe as it's a pretty common pattern 👍🏻
Thank you. I'll give this a try Yes I agree it's a good candidate.
Ok @cherniavskii I gave it a try. Your sandbox worked perfectly but when I integrated it into our code base the state still wouldn't save. I've forked the sandbox to show how a reload isn't working using my code https://codesandbox.io/s/datagridpremium-basic-forked-khqvcm?file=/src/CommonGrid.jsx
just in case the sandbox has issues like it did in the past below is the code
import React from "react";
import { Box } from '@mui/material';
import CircularProgress from "@mui/material/CircularProgress";
import {
DataGridPremium,
GridToolbarContainer,
GridToolbarColumnsButton,
GridToolbarFilterButton,
GridToolbarExport,
GridToolbarDensitySelector,
gridClasses,
useGridApiRef,
} from '@mui/x-data-grid-premium';
import PropTypes from "prop-types";
import { alpha } from "@mui/material/styles";
import { styled } from "@mui/material/styles";
const ODD_OPACITY = 0.2;
//class actions
function saveStateSnapshotToLocalStorage(stateSnapshot) {
console.info(
"State update: Update the state snap in localStorage",
stateSnapshot
);
localStorage.setItem("dataGridState", JSON.stringify(stateSnapshot));
}
function getStateSnapshotFromLocalStorage() {
const stringifiedState = localStorage.getItem("dataGridState");
return new Promise((resolve) => {
if (!stringifiedState) {
return resolve({});
}
const stateSnapshot = JSON.parse(stringifiedState);
return resolve(stateSnapshot);
});
}
const StripedDataGrid = styled(DataGridPremium)(({ theme }) => ({
[`& .${gridClasses.row}.even`]: {
backgroundColor: theme.palette.grey[200],
"&:hover, &.Mui-hovered": {
backgroundColor: alpha(theme.palette.primary.main, ODD_OPACITY),
"@media (hover: none)": {
backgroundColor: "transparent"
}
},
"&.Mui-selected": {
backgroundColor: alpha(
theme.palette.primary.main,
ODD_OPACITY + theme.palette.action.selectedOpacity
),
"&:hover, &.Mui-hovered": {
backgroundColor: alpha(
theme.palette.primary.main,
ODD_OPACITY +
theme.palette.action.selectedOpacity +
theme.palette.action.hoverOpacity
),
// Reset on touch devices, it doesn't add specificity
"@media (hover: none)": {
backgroundColor: alpha(
theme.palette.primary.main,
ODD_OPACITY + theme.palette.action.selectedOpacity
)
}
}
}
}
}));
const StyledGridOverlay = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
'& .ant-empty-img-1': {
fill: theme.palette.mode === 'light' ? '#aeb8c2' : '#262626',
},
'& .ant-empty-img-2': {
fill: theme.palette.mode === 'light' ? '#f5f5f7' : '#595959',
},
'& .ant-empty-img-3': {
fill: theme.palette.mode === 'light' ? '#dce0e6' : '#434343',
},
'& .ant-empty-img-4': {
fill: theme.palette.mode === 'light' ? '#fff' : '#1c1c1c',
},
'& .ant-empty-img-5': {
fillOpacity: theme.palette.mode === 'light' ? '0.8' : '0.08',
fill: theme.palette.mode === 'light' ? '#f5f5f5' : '#fff',
},
}));
function CustomNoRowsOverlay() {
return (
<StyledGridOverlay>
<svg
width="120"
height="100"
viewBox="0 0 184 152"
aria-hidden
focusable="false"
>
<g fill="none" fillRule="evenodd">
<g transform="translate(24 31.67)">
<ellipse
className="ant-empty-img-5"
cx="67.797"
cy="106.89"
rx="67.797"
ry="12.668"
/>
<path
className="ant-empty-img-1"
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z"
/>
<path
className="ant-empty-img-2"
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z"
/>
<path
className="ant-empty-img-3"
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z"
/>
</g>
<path
className="ant-empty-img-3"
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z"
/>
<g className="ant-empty-img-4" transform="translate(149.65 15.383)">
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815" />
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z" />
</g>
</g>
</svg>
<Box sx={{ mt: 1, fontSize: 16, fontWeight: 'bolder' }}>No Data</Box>
</StyledGridOverlay>
);
}
const CommonGrid = ({ /*columns, rows, tableTitle, sortingOrder, sortModel, onSortModelChange, */
actionButton, handleSelectedItems, ...other }) => {
const apiRef = useGridApiRef();
const [cachedInitialState, setCachedInitialState] = React.useState();
const [rowSelectionModel, setRowSelectionModel] = React.useState([]);
const [checkboxSelection, setCheckboxSelection] = React.useState(true);
const [sortModel, setSortModel] = React.useState([
{
field: "user",
sort: "desc"
},
{ field: "detected_on", desc: "desc" }
]);
React.useEffect(() => {
const getSnapshot = async () => {
const snapshotFromLocalStorage = await getStateSnapshotFromLocalStorage();
console.log(
"Page Mount: Recovered state from localStorage",
snapshotFromLocalStorage
);
setCachedInitialState(snapshotFromLocalStorage);
};
getSnapshot();
}, []);
const saveSnapshot = React.useCallback(() => {
const stateSnapshot = apiRef.current.exportState();
saveStateSnapshotToLocalStorage(stateSnapshot);
}, [apiRef]);
if (!cachedInitialState) {
return <CircularProgress />;
}
function CustomToolbar() {
return (
<GridToolbarContainer sx={{
width: "100%",
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}} >
<GridToolbarColumnsButton sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}} />
<GridToolbarFilterButton sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}} />
<GridToolbarDensitySelector sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}} />
<GridToolbarExport sx={{
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
}} />
{actionButton ? actionButton : null}
</GridToolbarContainer>
);
}
return (
<Box
sx={{
height: "100%",
width: "100%",
"& .datagridHeader": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiDataGrid-footerContainer": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiTablePagination-root": {
backgroundColor: "#4d7496",
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder"
},
"& .MuiTablePagination-displayedRows": {
color: "#fff !important",
fontSize: 13,
fontWeight: "bolder",
},
"& .MuiSelect-standard": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important",
},
"& .MuiTablePagination-actions": {
fontSize: 13,
fontWeight: "bolder",
color: "#fff !important",
"& .MuiButtonBase-root": {
color: "#fff !important",
fontSize: 13,
},
},
"& .MuiTablePagination-selectLabel": {
fontSize: 13,
fontWeight: "bolder",
color: "#fff !important",
},
"& .MuiTablePagination-selectIcon": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important",
},
"& .MuiTablePagination-selectRoot": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important",
},
"& .MuiTablePagination-input": {
fontSize: "13px !important",
fontWeight: "bolder",
color: "#fff !important",
},
"& .MuiDataGrid-columnHeader": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700
},
"& .MuiDataGrid-columnHeaderCheckbox": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700
},
"& .MuiDataGrid-cell": {
border: "1px solid rgb(224, 224, 224)",
fontSize: 13
},
"& .MuiDataGrid-root .MuiDataGrid-cell": {
whiteSpace: "normal !important",
wordWwrap: "break-word !important",
textAlign: 'left'
},
"& .MuiDataGrid-sortIcon": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700,
opacity: "inherit !important"
},
"& .MuiDataGrid-filterIcon": {
backgroundColor: "#4d7496",
color: "#fff",
fontSize: 13,
fontWeight: 700,
opacity: "inherit !important"
},
"& .MuiDataGrid-columnHeaderTitle": {
fontWeight: 'bolder'
}
}} >
{/* {tableTitle} */}TESTING saving state
<br />
<br />
<StripedDataGrid
apiRef={apiRef}
onStateChange={saveSnapshot}
columns={[
{
id: "user",
headerName: "User",
field: "user",
type: "string",
flex: 1,
aggregable: false,
groupable: false,
headerAlign: "center",
valueGetter: (params) => {
// console.log('in value getting for User params: ', params)
//Asided from everything else, must set somethign in maybe name or a field to do the filter on
return params.value.name;
},
renderCell: (rowInfo) => {
// console.log('valueFormatter for User: ', rowInfo) //you need to verify values below
if (rowInfo.row.user.name) {
return <div> placeHolder </div>;
} else {
return <span>Missing Name Info 2</span>;
}
}
},
{
id: "violation_type",
headerName: "Violation Type",
field: "is_role",
flex: 1,
headerAlign: "center",
aggregable: false,
groupable: false,
//Change to value gettter
renderCell: (rowInfo) => {
return rowInfo.value;
},
valueGetter: (params) => {
// console.log('The violation type value getter ', params, ' \n params.row.is_role: ', params.row.is_role, ' trying value too: ', params.value)
if (params.row.is_role) {
return "Unqualified User (Role)";
} else {
return "Unassigned User (Accessor)";
}
}
},
{
field: "accessor_or_role",
headerName: "Accessor or Role",
flex: 1,
headerAlign: "center",
aggregable: false,
groupable: false,
//This allows for rendering a react component
renderCell: (rowInfo) => {
// console.log(' Ist accessor or role: ', rowInfo)
if (rowInfo.row.is_role) {
return <div> placeHolder </div>;
}
return <div> placeHolder </div>;
},
valueGetter: (params) => {
// console.log('The violation type value getter ', params, ' \n params.row.is_role: ', params.row.is_role, ' trying value too: ', params.value)
if (params.row.is_role) {
return params.row.role.name;
} else {
return params.row.accessor.name;
}
}
},
{
id: "detected_on",
headerName: "Detected On",
field: "created_date",
flex: 1,
type: "date",
headerAlign: "center",
aggregable: false,
groupable: false,
valueFormatter: (params) => {
//console.log('VF The created_date on params: ', params)
if (params.value == null) {
return "";
}
return params.value;
},
valueGetter: (params) => {
// console.log('VG the created_date in params: ', params)
if (params.row.created_date == null) {
return "";
}
// console.log('created_date The last modified from row: ', params.row.created_date)
const date = new Date(params.row.created_date);
// console.log('to return the created_date in getter: ', date)
return date;
}
},
{
id: "last_updated",
headerName: "Last Updated",
field: "last_modified",
flex: 1,
type: "date",
headerAlign: "center",
aggregable: false,
groupable: false,
valueFormatter: (params) => {
// console.log('VF The last_modified on params: ', params)
if (params.value == null) {
return "";
}
return params.value;
},
valueGetter: (params) => {
// console.log('VG the last_modified in params: ', params)
if (params.row.last_modified == null) {
return "";
}
//console.log('last_modified The last modified from row: ', params.row.last_modified)
const date = new Date(params.row.last_modified);
//console.log('to return the last_modified in getter: ', date)
return date;
}
},
{
headerName: "Suspension",
type: "string",
field: "rolesuspension",
flex: 1,
aggregable: false,
groupable: false,
headerAlign: "center",
renderCell: (rowInfo) => {
// console.log('Row info rolesuspension to view: ', rowInfo)
if (!rowInfo.row.is_role) {
return (
<span>
<i>N/A</i>
</span>
);
}
const roleSus = rowInfo.row.rolesuspension;
//console.log(rowInfo, ' = the rolesuspension ', roleSus)
if (!roleSus || roleSus.cleared_date) {
return <span>None</span>;
}
if (roleSus.suspension_activated_date) {
return (
<span>
<span
className="fa fa-minus-circle text-danger"
role="img"
aria-hidden={true}
/>{" "}
Suspended {roleSus.suspension_activated_date}
</span>
);
}
const daysRemaining = 4;
return (
<span>
<span
className="fa fa-hourglass-half"
role="img"
aria-hidden={true}
/>{" "}
{daysRemaining} day{daysRemaining != 1 && "s"} until suspension
</span>
);
},
valueGetter: (rowInfo) => {
// console.log('Row info rolesuspension: ', rowInfo)
if (!rowInfo.row.is_role) {
//may need to use the is_role property This needs to be checked HS and maybe a more examples ffor this poroperty
return "N/A";
}
const roleSus = rowInfo.value;
if (!roleSus || roleSus.cleared_date) {
return "None";
}
if (roleSus.suspension_activated_date) {
return "Suspended" + roleSus.suspension_activated_date;
}
const daysRemaining = 4;
let daysVerbiage = "";
if (daysRemaining > 1) {
daysVerbiage = daysRemaining + " days until suspension";
} else {
daysVerbiage = daysRemaining + " day until suspension";
}
return daysVerbiage;
}
},
]}
rows={[
{
id: 1,
uuid: "123",
user: {
sid: "1234",
name: "Apple Cider 2",
first_name: "Apple",
last_name: "Cider 2",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April is A ROLE",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "An accessor Name",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: true,
created_date: "2023-02-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-03-11T14:51:39Z",
rolesuspension: null
},
{
id: 2,
uuid: "123ab",
user: {
sid: "1234",
name: "Samy Zebra",
first_name: "Samy",
last_name: "Zebra",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
adm_account: false,
role: {
name: "April Role",
description: "#f00",
url: "www.twitter.com",
uuid: "7890",
parent_object: null,
userCount: 4,
my_permissions: [
{
add_users: true,
create: true,
read: true,
update: true,
delete: true,
grant_user_suspensions: true,
set_rules: true
}
]
},
accessor: {
sid: "1234",
name: "Not a Role Accessor Name BOO",
first_name: " bfgr",
last_name: "fererer",
email: "fererer",
is_active: true,
user_type: "user",
duty_org: "NSA",
org: "trrere",
has_adm_account: true,
url: "www.google.com",
uuid: "fferer",
my_permissions: {
create: true,
update: true,
destroy: true,
write: true,
read: true
},
objectType: "user",
entity_types: []
},
is_role: false,
created_date: "2023-01-11T14:51:39Z",
cleared_date: "2023-06-11T14:51:39Z",
url: "www.fake.com",
last_modified: "2023-05-11T14:51:39Z",
rolesuspension: null
}
]}
sx={{
width: '100% !important',
".MuiDataGrid-iconButtonContainer": {
visibility: "visible"
},
".MuiDataGrid-sortIcon": {
opacity: "inherit !important"
},
".MuiTouchRipple-root": {
opacity: "inherit !important",
visibility: "visible !important"
},
".MuiDataGrid-menuIconButton": {
color: "#fff"
},
".MuiDataGrid-columnHeaderTitleContainerContent .MuiCheckbox-root": {
color: '#ffff !important',
fontSize: 14,
fontWeight: 'bolder'
},
'& .MuiDataGrid-virtualScroller::-webkit-scrollbar': {
width: '0.4em',
},
'& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track': {
color: '#4d7496',
},
'& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb': {
backgroundColor: '#4d7496',
color: '#fff'
},
'& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover': {
backgroundColor: '#4d7496',
color: '#fff'
},
'& .MuiDataGrid-iconButtonContainer': {
marginLeft: '25px',
visibility: 'visible !important',
width: 'auto !important',
},
"& .MuiDataGrid-cell--withRenderer": {
fontSize: "13px !important"
},
"& .MuiDataGrid-menuIcon": {
fontSize: 14,
visibility: 'visible !important',
width: 'auto !important',
fontWeight: 'bolder',
"& .MuiSvgIcon-root": {
fontSize: 14,
visibility: 'visible !important',
width: 'auto !important',
fontWeight: 'bolder',
}
},
}}
getDetailPanelContent={other.getDetailPanelContent}
getDetailPanelHeight={() => 'auto'}
rowThreshold={other.rowThreshold}
keepColumnPositionIfDraggedOutside={true}
loading={false}
paginationMode={"client"}
slots={{
noRowsOverlay: CustomNoRowsOverlay,
toolbar: CustomToolbar,
}}
checkboxSelection
experimentalFeatures={{
ariaV7: true,
newEditingApi: true
}}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? "even" : "odd"
}
initialState={{
...cachedInitialState,
sorting: {
sortModel: sortModel
}
}}
sortingOrder={["desc", "asc"]}
sortModel={sortModel}
onSortModelChange={(model) => {
// console.log('****************** The column sort order chagnes \n\n\n\n')
setSortModel(model); //set the state
}}
pagination
/>
</Box>
);
};
/* CommonGrid.propTypes = {
columns: PropTypes.array,
rows: PropTypes.array,
tableTitle: PropTypes.string
}; */
export default CommonGrid;
So after fooling with this a bit more I got it working. Not sure why the example didn't work but after playing in the my code the state was saved and retrieved as expected.
if using multiple grids in the same app, give the local storage a unique name for each. Otherwise the properties will get mingled.
The problem in depth 🔍
Looking at the options in the demo https://mui.com/x/react-data-grid/state/#save-and-restore-the-stateto save and restore state, there isn't anything that shows this being able to be automatic once the user sorts/filters, etc. also can the state be restored on refreshes and page open and closes
Your environment 🌎
`npx @mui/envinfo`
``` Don't forget to mention which browser you used. Output from `npx @mui/envinfo` goes here. ```Search keywords: automatically save and restore the datagrid state Order ID: 66765