From 343d1add689f7d0ea870f320696729261efe8f40 Mon Sep 17 00:00:00 2001 From: Tobias Merki <103021062+neotob@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:43:53 -1000 Subject: [PATCH 1/5] usePersistentReactDataTableState added --- CHANGELOG.md | 4 + package.json | 1 + src/index.ts | 1 + .../usePersistentReactDataTableState.ts | 97 +++++++++++++++++++ yarn.lock | 15 ++- 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/lib/useReactDataTableState/usePersistentReactDataTableState.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c88a310..72d4649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- added the `usePersistentReactDataTableState` hook which saves it's own state into the local storage + ## [5.14.0] - 2025-12-15 ### Fixed diff --git a/package.json b/package.json index 23ede09..d071cc2 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", + "@neolution-ch/javascript-utils": "^2.2.0", "@neolution-ch/react-pattern-ui": "^5.3.0", "@tanstack/react-table": "^8.12.0", "@tanstack/react-virtual": "^3.13.12", diff --git a/src/index.ts b/src/index.ts index d5c2c1e..e62657c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from "./lib/translations/translations"; export * from "./lib/useReactDataTableState/useReactDataTableState"; +export * from "./lib/useReactDataTableState/usePersistentReactDataTableState"; export * from "./lib/useReactDataTable/useReactDataTable"; export * from "./lib/ReactDataTable/ReactDataTable"; export * from "./lib/utils/getStronglyTypedColumnFilter"; diff --git a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts new file mode 100644 index 0000000..ba5ceea --- /dev/null +++ b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts @@ -0,0 +1,97 @@ +import { getLocalStorageItem, setLocalStorageItem } from "@neolution-ch/javascript-utils"; +import { OptionalNullable } from "../types/NullableTypes"; +import { useReactDataTableStateProps } from "./useReactDataTableStateProps"; +import { useReactDataTableStateResult } from "./useReactDataTableStateResult"; +import { useReactDataTableState } from "./useReactDataTableState"; +import { useEffect } from "react"; + +const usePersistentReactDataTableState = >( + props: OptionalNullable> & { localStorageKey: string }, +): useReactDataTableStateResult => { + const { + pagination, + setPagination, + columnFilters, + columnPinning, + expanded, + rowSelection, + sorting, + setColumnFilters, + afterSearchFilter, + setAfterSearchFilter, + setColumnPinning, + setExpanded, + setRowSelection, + setSorting, + } = useReactDataTableState({ + initialColumnPinning: + getLocalStorageItem["initialColumnPinning"]>(`${props.localStorageKey}_columnPinning`) ?? + props?.initialColumnPinning, + initialExpanded: + getLocalStorageItem["initialExpanded"]>(`${props.localStorageKey}_expanded`) ?? + props?.initialExpanded, + initialPagination: + getLocalStorageItem["initialPagination"]>(`${props.localStorageKey}_pagination`) ?? + props?.initialPagination, + initialRowSelection: + getLocalStorageItem["initialRowSelection"]>(`${props.localStorageKey}_rowSelection`) ?? + props?.initialRowSelection, + initialSorting: + getLocalStorageItem["initialSorting"]>(`${props.localStorageKey}_sorting`) ?? + props?.initialSorting, + } as OptionalNullable>); + + useEffect(() => { + // We can't set these values directly in the useReactDataTableState hook above, + // because the filter would be taken as default value also for the reset function. + const storedColumnFilters = getLocalStorageItem(`${props.localStorageKey}_columnFilters`); + if (storedColumnFilters) { + setColumnFilters(storedColumnFilters); + } + + const storedAfterSearchFilter = getLocalStorageItem(`${props.localStorageKey}_afterSearchFilter`); + if (storedAfterSearchFilter) { + setAfterSearchFilter(storedAfterSearchFilter); + } + }, [props.localStorageKey, setColumnFilters, setAfterSearchFilter]); + + return { + pagination, + setPagination: (newPagination) => { + setPagination(newPagination); + setLocalStorageItem(`${props.localStorageKey}_pagination`, newPagination); + }, + columnFilters, + setColumnFilters: (newColumnFilters) => { + setColumnFilters(newColumnFilters); + setLocalStorageItem(`${props.localStorageKey}_columnFilters`, newColumnFilters); + }, + afterSearchFilter, + setAfterSearchFilter: (newAfterSearchFilter) => { + setAfterSearchFilter(newAfterSearchFilter); + setLocalStorageItem(`${props.localStorageKey}_afterSearchFilter`, newAfterSearchFilter); + }, + columnPinning, + setColumnPinning: (newColumnPinning) => { + setColumnPinning(newColumnPinning); + setLocalStorageItem(`${props.localStorageKey}_columnPinning`, newColumnPinning); + }, + expanded, + setExpanded: (newExpanded) => { + setExpanded(newExpanded); + setLocalStorageItem(`${props.localStorageKey}_expanded`, newExpanded); + }, + rowSelection, + setRowSelection: (newRowSelection) => { + setRowSelection(newRowSelection); + setLocalStorageItem(`${props.localStorageKey}_rowSelection`, newRowSelection); + }, + sorting, + setSorting: (newSorting) => { + setSorting(newSorting); + setLocalStorageItem(`${props.localStorageKey}_sorting`, newSorting); + }, + }; +}; + +export { usePersistentReactDataTableState }; diff --git a/yarn.lock b/yarn.lock index eede2fc..72c8455 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1778,6 +1778,14 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@neolution-ch/javascript-utils@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@neolution-ch/javascript-utils/-/javascript-utils-2.2.0.tgz#11260342ef66f6ca1b3933282683b687bfe5a20a" + integrity sha512-2QO1l94EjiSbQEVDAKtWlaKJgWFj8hXY43AOKvYpNYJ7y6BbOZAwhbjEFKwgSpfVizpuyqHSsTPSHSKG8WNYhQ== + dependencies: + date-fns "^2.30.0" + uuid "^9.0.1" + "@neolution-ch/react-pattern-ui@^5.3.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@neolution-ch/react-pattern-ui/-/react-pattern-ui-5.3.0.tgz#c900bdfc6947cfbe108910425b4dcb4027cdf34f" @@ -5787,7 +5795,7 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.29.3: +date-fns@^2.29.3, date-fns@^2.30.0: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== @@ -14035,6 +14043,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-to-istanbul@^8.1.0: version "8.1.1" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" From 18c0c9a4de6ec5a192e1e7876fb0799f3a6839ce Mon Sep 17 00:00:00 2001 From: Tobias Merki Date: Wed, 14 Jan 2026 21:13:14 -1000 Subject: [PATCH 2/5] update --- .../usePersistentReactDataTableState.ts | 22 ++++++------------- .../useReactDataTableState.ts | 13 ++++++++--- .../useReactDataTableStateProps.ts | 5 +++++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts index ba5ceea..ab94dc9 100644 --- a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts +++ b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts @@ -3,7 +3,6 @@ import { OptionalNullable } from "../types/NullableTypes"; import { useReactDataTableStateProps } from "./useReactDataTableStateProps"; import { useReactDataTableStateResult } from "./useReactDataTableStateResult"; import { useReactDataTableState } from "./useReactDataTableState"; -import { useEffect } from "react"; const usePersistentReactDataTableState = >( props: OptionalNullable> & { localStorageKey: string }, @@ -39,22 +38,15 @@ const usePersistentReactDataTableState = ["initialSorting"]>(`${props.localStorageKey}_sorting`) ?? props?.initialSorting, + initialColumnFilters: + getLocalStorageItem["initialColumnFilters"]>(`${props.localStorageKey}_columnFilters`) ?? + props?.initialColumnFilters, + initialAfterSearchFilter: + getLocalStorageItem["initialAfterSearchFilter"]>( + `${props.localStorageKey}_afterSearchFilter`, + ) ?? props?.initialAfterSearchFilter, } as OptionalNullable>); - useEffect(() => { - // We can't set these values directly in the useReactDataTableState hook above, - // because the filter would be taken as default value also for the reset function. - const storedColumnFilters = getLocalStorageItem(`${props.localStorageKey}_columnFilters`); - if (storedColumnFilters) { - setColumnFilters(storedColumnFilters); - } - - const storedAfterSearchFilter = getLocalStorageItem(`${props.localStorageKey}_afterSearchFilter`); - if (storedAfterSearchFilter) { - setAfterSearchFilter(storedAfterSearchFilter); - } - }, [props.localStorageKey, setColumnFilters, setAfterSearchFilter]); - return { pagination, setPagination: (newPagination) => { diff --git a/src/lib/useReactDataTableState/useReactDataTableState.ts b/src/lib/useReactDataTableState/useReactDataTableState.ts index 375d5db..e4558a1 100644 --- a/src/lib/useReactDataTableState/useReactDataTableState.ts +++ b/src/lib/useReactDataTableState/useReactDataTableState.ts @@ -14,11 +14,18 @@ import { OptionalNullable } from "../types/NullableTypes"; const useReactDataTableState = >( props: OptionalNullable>, ): useReactDataTableStateResult => { - const { initialColumnFilters, initialSorting, initialPagination, initialRowSelection, initialExpanded, initialColumnPinning } = - props as useReactDataTableStateProps; + const { + initialColumnFilters, + initialSorting, + initialPagination, + initialRowSelection, + initialExpanded, + initialColumnPinning, + initialAfterSearchFilter, + } = props as useReactDataTableStateProps; const [columnFilters, setColumnFilters] = useState((initialColumnFilters ?? {}) as TFilter); - const [afterSearchFilter, setAfterSearchFilter] = useState((initialColumnFilters ?? {}) as TFilter); + const [afterSearchFilter, setAfterSearchFilter] = useState((initialAfterSearchFilter ?? initialColumnFilters ?? {}) as TFilter); const [sorting, setSorting] = useState | undefined>(initialSorting); const [rowSelection, setRowSelection] = useState(initialRowSelection ?? ({} as RowSelectionState)); const [expanded, setExpanded] = useState(initialExpanded ?? ({} as ExpandedState)); diff --git a/src/lib/useReactDataTableState/useReactDataTableStateProps.ts b/src/lib/useReactDataTableState/useReactDataTableStateProps.ts index 75f41f8..e10c839 100644 --- a/src/lib/useReactDataTableState/useReactDataTableStateProps.ts +++ b/src/lib/useReactDataTableState/useReactDataTableStateProps.ts @@ -12,6 +12,11 @@ export interface useReactDataTableStateProps */ initialColumnFilters: ColumnFilterState; + /** + * the initial after search filter + */ + initialAfterSearchFilter: ColumnFilterState; + /** * the initial sorting */ From ded750dbb96cef007b22e5f33da292bffe39f087 Mon Sep 17 00:00:00 2001 From: Tobias Merki Date: Thu, 15 Jan 2026 00:06:16 -1000 Subject: [PATCH 3/5] fix --- .../usePersistentReactDataTableState.ts | 47 ++++++++++++------- .../useReactDataTableStateProps.ts | 2 +- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts index ab94dc9..653e47c 100644 --- a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts +++ b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts @@ -3,10 +3,27 @@ import { OptionalNullable } from "../types/NullableTypes"; import { useReactDataTableStateProps } from "./useReactDataTableStateProps"; import { useReactDataTableStateResult } from "./useReactDataTableStateResult"; import { useReactDataTableState } from "./useReactDataTableState"; +import { FilterModel } from "../types/TableState"; -const usePersistentReactDataTableState = >( +/** + * A custom hook that will initialize all the state needed for the react data table and will persist it to local storage + * @param props The properties to configure the initial state + * @returns the state and the setters + */ +const usePersistentReactDataTableState = >( props: OptionalNullable> & { localStorageKey: string }, ): useReactDataTableStateResult => { + const { + initialColumnFilters, + initialSorting, + initialPagination, + initialRowSelection, + initialExpanded, + initialColumnPinning, + initialAfterSearchFilter, + localStorageKey, + } = props as useReactDataTableStateProps & { localStorageKey: string }; + const { pagination, setPagination, @@ -24,28 +41,26 @@ const usePersistentReactDataTableState = ({ initialColumnPinning: - getLocalStorageItem["initialColumnPinning"]>(`${props.localStorageKey}_columnPinning`) ?? - props?.initialColumnPinning, + getLocalStorageItem["initialColumnPinning"]>(`${localStorageKey}_columnPinning`) ?? + initialColumnPinning, initialExpanded: - getLocalStorageItem["initialExpanded"]>(`${props.localStorageKey}_expanded`) ?? - props?.initialExpanded, + getLocalStorageItem["initialExpanded"]>(`${localStorageKey}_expanded`) ?? initialExpanded, initialPagination: - getLocalStorageItem["initialPagination"]>(`${props.localStorageKey}_pagination`) ?? - props?.initialPagination, + getLocalStorageItem["initialPagination"]>(`${localStorageKey}_pagination`) ?? + initialPagination, initialRowSelection: - getLocalStorageItem["initialRowSelection"]>(`${props.localStorageKey}_rowSelection`) ?? - props?.initialRowSelection, + getLocalStorageItem["initialRowSelection"]>(`${localStorageKey}_rowSelection`) ?? + initialRowSelection, initialSorting: - getLocalStorageItem["initialSorting"]>(`${props.localStorageKey}_sorting`) ?? - props?.initialSorting, + getLocalStorageItem["initialSorting"]>(`${localStorageKey}_sorting`) ?? initialSorting, initialColumnFilters: - getLocalStorageItem["initialColumnFilters"]>(`${props.localStorageKey}_columnFilters`) ?? - props?.initialColumnFilters, + getLocalStorageItem["initialColumnFilters"]>(`${localStorageKey}_columnFilters`) ?? + initialColumnFilters, initialAfterSearchFilter: getLocalStorageItem["initialAfterSearchFilter"]>( - `${props.localStorageKey}_afterSearchFilter`, - ) ?? props?.initialAfterSearchFilter, - } as OptionalNullable>); + `${localStorageKey}_afterSearchFilter`, + ) ?? initialAfterSearchFilter, + } as useReactDataTableStateProps as OptionalNullable>); return { pagination, diff --git a/src/lib/useReactDataTableState/useReactDataTableStateProps.ts b/src/lib/useReactDataTableState/useReactDataTableStateProps.ts index e10c839..fa1deae 100644 --- a/src/lib/useReactDataTableState/useReactDataTableStateProps.ts +++ b/src/lib/useReactDataTableState/useReactDataTableStateProps.ts @@ -15,7 +15,7 @@ export interface useReactDataTableStateProps /** * the initial after search filter */ - initialAfterSearchFilter: ColumnFilterState; + initialAfterSearchFilter?: ColumnFilterState; /** * the initial sorting From 8dca9dfd64ac176dc4fe65afb3e3563ef35950fb Mon Sep 17 00:00:00 2001 From: Tobias Merki Date: Thu, 15 Jan 2026 17:09:08 -1000 Subject: [PATCH 4/5] update --- .../usePersistentReactDataTableState.ts | 78 ++++++++++++------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts index 653e47c..2418b8f 100644 --- a/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts +++ b/src/lib/useReactDataTableState/usePersistentReactDataTableState.ts @@ -4,6 +4,7 @@ import { useReactDataTableStateProps } from "./useReactDataTableStateProps"; import { useReactDataTableStateResult } from "./useReactDataTableStateResult"; import { useReactDataTableState } from "./useReactDataTableState"; import { FilterModel } from "../types/TableState"; +import { useCallback } from "react"; /** * A custom hook that will initialize all the state needed for the react data table and will persist it to local storage @@ -64,40 +65,61 @@ const usePersistentReactDataTableState = { - setPagination(newPagination); - setLocalStorageItem(`${props.localStorageKey}_pagination`, newPagination); - }, + setPagination: useCallback( + (newPagination) => { + setPagination(newPagination); + setLocalStorageItem(`${props.localStorageKey}_pagination`, newPagination); + }, + [props.localStorageKey, setPagination], + ), columnFilters, - setColumnFilters: (newColumnFilters) => { - setColumnFilters(newColumnFilters); - setLocalStorageItem(`${props.localStorageKey}_columnFilters`, newColumnFilters); - }, + setColumnFilters: useCallback( + (newColumnFilters) => { + setColumnFilters(newColumnFilters); + setLocalStorageItem(`${props.localStorageKey}_columnFilters`, newColumnFilters); + }, + [props.localStorageKey, setColumnFilters], + ), afterSearchFilter, - setAfterSearchFilter: (newAfterSearchFilter) => { - setAfterSearchFilter(newAfterSearchFilter); - setLocalStorageItem(`${props.localStorageKey}_afterSearchFilter`, newAfterSearchFilter); - }, + setAfterSearchFilter: useCallback( + (newAfterSearchFilter) => { + setAfterSearchFilter(newAfterSearchFilter); + setLocalStorageItem(`${props.localStorageKey}_afterSearchFilter`, newAfterSearchFilter); + }, + [props.localStorageKey, setAfterSearchFilter], + ), columnPinning, - setColumnPinning: (newColumnPinning) => { - setColumnPinning(newColumnPinning); - setLocalStorageItem(`${props.localStorageKey}_columnPinning`, newColumnPinning); - }, + setColumnPinning: useCallback( + (newColumnPinning) => { + setColumnPinning(newColumnPinning); + setLocalStorageItem(`${props.localStorageKey}_columnPinning`, newColumnPinning); + }, + [props.localStorageKey, setColumnPinning], + ), expanded, - setExpanded: (newExpanded) => { - setExpanded(newExpanded); - setLocalStorageItem(`${props.localStorageKey}_expanded`, newExpanded); - }, + setExpanded: useCallback( + (newExpanded) => { + setExpanded(newExpanded); + setLocalStorageItem(`${props.localStorageKey}_expanded`, newExpanded); + }, + [props.localStorageKey, setExpanded], + ), rowSelection, - setRowSelection: (newRowSelection) => { - setRowSelection(newRowSelection); - setLocalStorageItem(`${props.localStorageKey}_rowSelection`, newRowSelection); - }, + setRowSelection: useCallback( + (newRowSelection) => { + setRowSelection(newRowSelection); + setLocalStorageItem(`${props.localStorageKey}_rowSelection`, newRowSelection); + }, + [props.localStorageKey, setRowSelection], + ), sorting, - setSorting: (newSorting) => { - setSorting(newSorting); - setLocalStorageItem(`${props.localStorageKey}_sorting`, newSorting); - }, + setSorting: useCallback( + (newSorting) => { + setSorting(newSorting); + setLocalStorageItem(`${props.localStorageKey}_sorting`, newSorting); + }, + [props.localStorageKey, setSorting], + ), }; }; From e65f7285c26cf645c9208db27c504a3a48190085 Mon Sep 17 00:00:00 2001 From: Tobias Merki Date: Mon, 19 Jan 2026 22:53:19 -1000 Subject: [PATCH 5/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b43b88f..b351741 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - added the `usePersistentReactDataTableState` hook which saves it's own state into the local storage +- added `initialAfterSearchFilter` property to give the possebility, to set a different initial after filter search (fall back is still `initialColumnFilters`) ### Changed