Skip to content

Commit

Permalink
[DataGrid] Add recipe for persisting filters in local storage (#14208)
Browse files Browse the repository at this point in the history
  • Loading branch information
cherniavskii authored Aug 15, 2024
1 parent f964fef commit 3e2d3d0
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
85 changes: 85 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as React from 'react';
import { DataGrid, GridToolbar } from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];

const createFilterModelStore = () => {
let listeners = [];
const lsKey = 'gridFilterModel';
const emptyModel = 'null';

return {
subscribe: (callback) => {
listeners.push(callback);
return () => {
listeners = listeners.filter((listener) => listener !== callback);
};
},
getSnapshot: () => {
try {
return localStorage.getItem(lsKey) || emptyModel;
} catch (error) {
return emptyModel;
}
},
getServerSnapshot: () => {
return emptyModel;
},
update: (filterModel) => {
localStorage.setItem(lsKey, JSON.stringify(filterModel));
listeners.forEach((listener) => listener());
},
};
};

const usePersistedFilterModel = () => {
const [filterModelStore] = React.useState(createFilterModelStore);

const filterModelString = React.useSyncExternalStore(
filterModelStore.subscribe,
filterModelStore.getSnapshot,
filterModelStore.getServerSnapshot,
);

const filterModel = React.useMemo(() => {
try {
return JSON.parse(filterModelString) || undefined;
} catch (error) {
return undefined;
}
}, [filterModelString]);

return React.useMemo(
() => [filterModel, filterModelStore.update],
[filterModel, filterModelStore.update],
);
};

export default function FilteringLocalStorage() {
const { data } = useDemoData({
dataSet: 'Employee',
visibleFields: VISIBLE_FIELDS,
rowLength: 100,
});

const [filterModel, setFilterModel] = usePersistedFilterModel();

const onFilterModelChange = React.useCallback(
(newFilterModel) => {
setFilterModel(newFilterModel);
},
[setFilterModel],
);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
slots={{ toolbar: GridToolbar }}
filterModel={filterModel}
onFilterModelChange={onFilterModelChange}
/>
</div>
);
}
94 changes: 94 additions & 0 deletions docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from 'react';
import {
DataGrid,
DataGridProps,
GridFilterModel,
GridToolbar,
} from '@mui/x-data-grid';
import { useDemoData } from '@mui/x-data-grid-generator';

const VISIBLE_FIELDS = ['name', 'rating', 'country', 'dateCreated', 'isAdmin'];

const createFilterModelStore = () => {
let listeners: Array<() => void> = [];
const lsKey = 'gridFilterModel';
const emptyModel = 'null';

return {
subscribe: (callback: () => void) => {
listeners.push(callback);
return () => {
listeners = listeners.filter((listener) => listener !== callback);
};
},
getSnapshot: () => {
try {
return localStorage.getItem(lsKey) || emptyModel;
} catch (error) {
return emptyModel;
}
},

getServerSnapshot: () => {
return emptyModel;
},

update: (filterModel: GridFilterModel) => {
localStorage.setItem(lsKey, JSON.stringify(filterModel));
listeners.forEach((listener) => listener());
},
};
};

const usePersistedFilterModel = () => {
const [filterModelStore] = React.useState(createFilterModelStore);

const filterModelString = React.useSyncExternalStore(
filterModelStore.subscribe,
filterModelStore.getSnapshot,
filterModelStore.getServerSnapshot,
);

const filterModel = React.useMemo(() => {
try {
return (JSON.parse(filterModelString) as GridFilterModel) || undefined;
} catch (error) {
return undefined;
}
}, [filterModelString]);

return React.useMemo(
() => [filterModel, filterModelStore.update] as const,
[filterModel, filterModelStore.update],
);
};

export default function FilteringLocalStorage() {
const { data } = useDemoData({
dataSet: 'Employee',
visibleFields: VISIBLE_FIELDS,
rowLength: 100,
});

const [filterModel, setFilterModel] = usePersistedFilterModel();

const onFilterModelChange = React.useCallback<
NonNullable<DataGridProps['onFilterModelChange']>
>(
(newFilterModel) => {
setFilterModel(newFilterModel);
},
[setFilterModel],
);

return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
{...data}
slots={{ toolbar: GridToolbar }}
filterModel={filterModel}
onFilterModelChange={onFilterModelChange}
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<DataGrid
{...data}
slots={{ toolbar: GridToolbar }}
filterModel={filterModel}
onFilterModelChange={onFilterModelChange}
/>
8 changes: 8 additions & 0 deletions docs/data/data-grid/filtering-recipes/filtering-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ title: Data Grid - Filtering customization recipes

<p class="description">Advanced filtering customization recipes.</p>

## Persisting filters in local storage

You can persist the filters in the local storage to keep the filters applied after the page is reloaded.

In the demo below, the [`React.useSyncExternalStore` hook](https://react.dev/reference/react/useSyncExternalStore) is used to synchronize the filters with the local storage.

{{"demo": "FilteringLocalStorage.js", "bg": "inline", "defaultCodeOpen": false}}

## Quick filter outside of the grid

The [Quick Filter](/x/react-data-grid/filtering/quick-filter/) component is typically used in the Data Grid's Toolbar component slot.
Expand Down

0 comments on commit 3e2d3d0

Please sign in to comment.