Skip to content

Commit

Permalink
Support custom serializers in table hooks
Browse files Browse the repository at this point in the history
Signed-off-by: Radoslaw Szwajkowski <[email protected]>
  • Loading branch information
rszwajko committed Sep 9, 2024
1 parent 7cd3ebb commit 852df20
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ export const useActiveItemState = <
persistTo,
key: "activeItem",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { activeItemId, setActiveItemId };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ export const useExpansionState = <
persistTo,
key: "expandedCells",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { expandedCells, setExpandedCells };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,15 @@ export const useFilterState = <
}
: persistTo === "localStorage" || persistTo === "sessionStorage"
? { persistTo, key: "filters" }
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { filterValues, setFilterValues };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ export const usePaginationState = <
persistTo,
key: "pagination",
}
: persistTo === "custom"
? {
persistTo,
key: "filters",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
const { pageNumber, itemsPerPage } = paginationState || defaultValue;
Expand Down
9 changes: 9 additions & 0 deletions client/src/app/hooks/table-controls/sorting/useSortState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ export const useSortState = <
persistTo,
key: "sort",
}
: persistTo === "custom"
? {
persistTo,
key: "sort",
serialize: (val) =>
args.customPersistance?.write(JSON.stringify(val)),
deserialize: () =>
JSON.parse(args.customPersistance?.read() ?? "{}") ?? {},
}
: { persistTo }),
});
return { activeSort, setActiveSort };
Expand Down
12 changes: 11 additions & 1 deletion client/src/app/hooks/table-controls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ export type TableFeature =
* - "urlParams" (recommended) - URL query parameters. Persists on page reload, browser history buttons (back/forward) or loading a bookmark. Resets on page navigation.
* - "localStorage" - Browser localStorage API. Persists semi-permanently and is shared across all tabs/windows. Resets only when the user clears their browsing data.
* - "sessionStorage" - Browser sessionStorage API. Persists on page/history navigation/reload. Resets when the tab/window is closed.
* - callback - custom solutions
*/
export type PersistTarget =
| "state"
| "urlParams"
| "localStorage"
| "sessionStorage";
| "sessionStorage"
| "custom";

/**
* Common persistence-specific args
Expand Down Expand Up @@ -107,6 +109,10 @@ export type IFeaturePersistenceArgs<
* Where to persist state for this feature.
*/
persistTo?: PersistTarget;
customPersistance?: {
write: (params: string) => void;
read: () => string;
};
};

export interface ColumnSetting {
Expand All @@ -132,6 +138,10 @@ export type ITablePersistenceArgs<
persistTo?:
| PersistTarget
| Partial<Record<TableFeature | "default", PersistTarget>>;
customPersistance?: {
write: (value: string, key: TableFeature) => void;
read: (key: TableFeature) => string;
};
};

/**
Expand Down
54 changes: 39 additions & 15 deletions client/src/app/hooks/table-controls/useTableControlState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
IFeaturePersistenceArgs,
ITableControlState,
ITablePersistenceArgs,
IUseTableControlStateArgs,
PersistTarget,
TableFeature,
} from "./types";
import { useFilterState } from "./filtering";
Expand All @@ -11,6 +12,30 @@ import { useActiveItemState } from "./active-item";
import { useExpansionState } from "./expansion";
import { useColumnState } from "./column/useColumnState";

const getPersistTo = ({
feature,
persistTo,
customPersistance,
}: {
feature: TableFeature;
persistTo: ITablePersistenceArgs["persistTo"];
customPersistance: ITablePersistenceArgs["customPersistance"];
}): {
persistTo: IFeaturePersistenceArgs["persistTo"];
customPersistance?: IFeaturePersistenceArgs["customPersistance"];
} => ({
persistTo:
!persistTo || typeof persistTo === "string"
? persistTo
: persistTo[feature] || persistTo.default,
customPersistance: customPersistance
? {
read: () => customPersistance?.read(feature) ?? "",
write: (val: string) => customPersistance?.write(val, feature),
}
: undefined,
});

/**
* Provides the "source of truth" state for all table features.
* - State can be persisted in one or more configurable storage targets, either the same for the entire table or different targets per feature.
Expand Down Expand Up @@ -41,31 +66,30 @@ export const useTableControlState = <
TFilterCategoryKey,
TPersistenceKeyPrefix
> => {
const getPersistTo = (feature: TableFeature): PersistTarget | undefined =>
!args.persistTo || typeof args.persistTo === "string"
? args.persistTo
: args.persistTo[feature] || args.persistTo.default;

const { customPersistance, persistTo, ...rest } = args;
const filterState = useFilterState<
TItem,
TFilterCategoryKey,
TPersistenceKeyPrefix
>({ ...args, persistTo: getPersistTo("filter") });
>({
...rest,
...getPersistTo({ feature: "filter", customPersistance, persistTo }),
});
const sortState = useSortState<TSortableColumnKey, TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("sort"),
...rest,
...getPersistTo({ feature: "sort", customPersistance, persistTo }),
});
const paginationState = usePaginationState<TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("pagination"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "pagination" }),
});
const expansionState = useExpansionState<TColumnKey, TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("expansion"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "expansion" }),
});
const activeItemState = useActiveItemState<TPersistenceKeyPrefix>({
...args,
persistTo: getPersistTo("activeItem"),
...rest,
...getPersistTo({ customPersistance, persistTo, feature: "activeItem" }),
});

const { columnNames, tableName, initialColumns } = args;
Expand Down
39 changes: 39 additions & 0 deletions client/src/app/hooks/usePersistentState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import { DisallowCharacters } from "@app/utils/type-utils";

type PersistToStateOptions = { persistTo?: "state" };

type PersistToCustomHookOptions<TValue> = {
persistTo: "custom";
key: string;
defaultValue: TValue;
isEnabled?: boolean;
serialize: (params: TValue) => void;
deserialize: () => TValue;
};

type PersistToUrlParamsOptions<
TValue,
TPersistenceKeyPrefix extends string,
Expand All @@ -33,6 +42,7 @@ export type UsePersistentStateOptions<
| PersistToStateOptions
| PersistToUrlParamsOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
| PersistToStorageOptions<TValue>
| PersistToCustomHookOptions<TValue>
);

export const usePersistentState = <
Expand Down Expand Up @@ -92,7 +102,36 @@ export const usePersistentState = <
? { ...options, key: prefixKey(options.key) }
: { ...options, isEnabled: false, key: "" }
),
custom: useCustomHook(
isCustomHookOptions(options)
? options
: {
key: "",
serialize: () => {},
deserialize: () => defaultValue,
defaultValue,
isEnabled: false,
persistTo: "custom",
}
),
};
const [value, setValue] = persistence[persistTo || "state"];
return isEnabled ? [value, setValue] : [defaultValue, () => {}];
};

const useCustomHook = <TValue>({
serialize,
deserialize,
}: PersistToCustomHookOptions<TValue>): [TValue, (val: TValue) => void] => {
return [deserialize(), serialize];
};

export const isCustomHookOptions = <
TValue,
TPersistenceKeyPrefix extends string,
TURLParamKey extends string,
>(
o: Partial<
UsePersistentStateOptions<TValue, TPersistenceKeyPrefix, TURLParamKey>
>
): o is PersistToCustomHookOptions<TValue> => o.persistTo === "custom";

0 comments on commit 852df20

Please sign in to comment.