From a506b9254431c07b1cbbaad3ea80c5da8dfd1b30 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:21:03 +0100 Subject: [PATCH] feat: Refactor Table State Persistence in Feature Toggle List (#5527) new custom hook, `usePersistentTableState` Co-authored-by: Mateusz Kwasniewski --- .../FeatureToggleListTable.tsx | 31 +++++++------- frontend/src/hooks/usePersistentTableState.ts | 40 +++++++++++++++++++ frontend/src/utils/serializeQueryParams.ts | 30 ++++++++++++++ 3 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 frontend/src/hooks/usePersistentTableState.ts create mode 100644 frontend/src/utils/serializeQueryParams.ts diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx index 3997140755c9..3638d65d7931 100644 --- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx +++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx @@ -46,13 +46,9 @@ import { useFeatureSearch, } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch'; import mapValues from 'lodash.mapvalues'; -import { - BooleanParam, - NumberParam, - StringParam, - useQueryParams, - withDefault, -} from 'use-query-params'; +import { NumberParam, StringParam, withDefault } from 'use-query-params'; +import { BooleansStringParam } from 'utils/serializeQueryParams'; +import { usePersistentTableState } from 'hooks/usePersistentTableState'; export const featuresPlaceholder = Array(15).fill({ name: 'Name of the feature', @@ -76,14 +72,19 @@ export const FeatureToggleListTable: VFC = () => { const { setToastApiError } = useToast(); const { uiConfig } = useUiConfig(); - const [tableState, setTableState] = useQueryParams({ - offset: withDefault(NumberParam, 0), - limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT), - query: StringParam, - favoritesFirst: withDefault(BooleanParam, true), - sortBy: withDefault(StringParam, 'createdAt'), - sortOrder: withDefault(StringParam, 'desc'), - }); + + const [tableState, setTableState] = usePersistentTableState( + 'features-list-table', + { + offset: withDefault(NumberParam, 0), + limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT), + query: StringParam, + favoritesFirst: withDefault(BooleansStringParam, true), + sortBy: withDefault(StringParam, 'createdAt'), + sortOrder: withDefault(StringParam, 'desc'), + }, + ); + const { features = [], total, diff --git a/frontend/src/hooks/usePersistentTableState.ts b/frontend/src/hooks/usePersistentTableState.ts new file mode 100644 index 000000000000..73925fd8e844 --- /dev/null +++ b/frontend/src/hooks/usePersistentTableState.ts @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { createLocalStorage } from 'utils/createLocalStorage'; +import { useQueryParams } from 'use-query-params'; + +const usePersistentSearchParams = (key: string) => { + const [searchParams, setSearchParams] = useSearchParams(); + const { value, setValue } = createLocalStorage(key, {}); + useEffect(() => { + const params = Object.fromEntries(searchParams.entries()); + if (Object.keys(params).length > 0) { + return; + } + if (Object.keys(value).length === 0) { + return; + } + + setSearchParams(value, { replace: true }); + }, []); + + return setValue; +}; + +export const usePersistentTableState = < + T extends Parameters[0], +>( + key: string, + queryParamsDefinition: T, +) => { + const updateStoredParams = usePersistentSearchParams(key); + + const [tableState, setTableState] = useQueryParams(queryParamsDefinition); + + useEffect(() => { + const { offset, ...rest } = tableState; + updateStoredParams(rest); + }, [JSON.stringify(tableState)]); + + return [tableState, setTableState] as const; +}; diff --git a/frontend/src/utils/serializeQueryParams.ts b/frontend/src/utils/serializeQueryParams.ts new file mode 100644 index 000000000000..80a72df65cf5 --- /dev/null +++ b/frontend/src/utils/serializeQueryParams.ts @@ -0,0 +1,30 @@ +// Custom additional serializers for query params library +// used in `useQueryParams` hook + +const encodeBoolean = ( + bool: boolean | null | undefined, +): string | null | undefined => { + if (bool == null) { + return bool; + } + + return bool ? 'true' : 'false'; +}; + +const decodeBoolean = ( + input: string | (string | null)[] | null | undefined, +): boolean | null | undefined => { + if (input === 'true') { + return true; + } + if (input === 'false') { + return false; + } + + return null; +}; + +export const BooleansStringParam = { + encode: encodeBoolean, + decode: decodeBoolean, +};