From 3e2d3d0bf1eaae184dccad96fc223b65099d7bbc Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Thu, 15 Aug 2024 13:39:55 +0200 Subject: [PATCH] [DataGrid] Add recipe for persisting filters in local storage (#14208) --- .../FilteringLocalStorage.js | 85 +++++++++++++++++ .../FilteringLocalStorage.tsx | 94 +++++++++++++++++++ .../FilteringLocalStorage.tsx.preview | 6 ++ .../filtering-recipes/filtering-recipes.md | 8 ++ 4 files changed, 193 insertions(+) create mode 100644 docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js create mode 100644 docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx create mode 100644 docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js new file mode 100644 index 0000000000000..290e5ce9e6084 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.js @@ -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 ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx new file mode 100644 index 0000000000000..f0d4089961706 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx @@ -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 + >( + (newFilterModel) => { + setFilterModel(newFilterModel); + }, + [setFilterModel], + ); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview new file mode 100644 index 0000000000000..cdca9c3dde546 --- /dev/null +++ b/docs/data/data-grid/filtering-recipes/FilteringLocalStorage.tsx.preview @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/filtering-recipes/filtering-recipes.md b/docs/data/data-grid/filtering-recipes/filtering-recipes.md index fa5322aa7f224..9265e2dd36f9a 100644 --- a/docs/data/data-grid/filtering-recipes/filtering-recipes.md +++ b/docs/data/data-grid/filtering-recipes/filtering-recipes.md @@ -6,6 +6,14 @@ title: Data Grid - Filtering customization recipes

Advanced filtering customization recipes.

+## 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.