fix: create TableFilterStateValue type

This commit is contained in:
ValdiANS
2026-04-29 15:10:25 +07:00
parent 9ea1d06972
commit 29347c24f4
+48 -40
View File
@@ -1,13 +1,29 @@
import { useCallback, useEffect, useMemo, useReducer } from 'react';
import { useTableFilterStore } from '@/stores/table/table-filter.store';
import { OptionType } from '@/components/input/SelectInput';
type TableFilterStateValue =
| undefined
| null
| boolean
| string
| string[]
| number
| number[]
| OptionType<number>
| OptionType<number>[]
| OptionType<string>
| OptionType<string>[];
/** Core filter shape (page + pageSize) extended by your custom fields */
export type TableFilterState<TExtra extends Record<string, unknown>> = {
export type TableFilterState<
TExtra extends Record<string, TableFilterStateValue>,
> = {
page: number;
pageSize: number;
} & TExtra;
type Action<TExtra extends Record<string, unknown>> =
type Action<TExtra extends Record<string, TableFilterStateValue>> =
| { type: 'SET_PAGE'; page: number }
| { type: 'SET_PAGE_SIZE'; pageSize: number; resetPage?: boolean }
| { type: 'SET_FILTERS'; filters: Partial<TExtra>; resetPage?: boolean }
@@ -20,7 +36,9 @@ type Action<TExtra extends Record<string, unknown>> =
| { type: 'REPLACE_ALL'; next: TableFilterState<TExtra> }
| { type: 'RESET' };
export type UseTableFilterOptions<TExtra extends Record<string, unknown>> = {
export type UseTableFilterOptions<
TExtra extends Record<string, TableFilterStateValue>,
> = {
/** Initial state; anything you omit falls back to defaults */
initial?: Partial<TableFilterState<TExtra>>;
/** Called after any state change */
@@ -43,9 +61,9 @@ function clampToInt(n: number, min = 1) {
return v < min ? min : v;
}
function createInitialState<TExtra extends Record<string, unknown>>(
opts: UseTableFilterOptions<TExtra> | undefined
): TableFilterState<TExtra> {
function createInitialState<
TExtra extends Record<string, TableFilterStateValue>,
>(opts: UseTableFilterOptions<TExtra> | undefined): TableFilterState<TExtra> {
const defaults = {
page: 1,
pageSize: opts?.defaultPageSize ?? 10,
@@ -59,10 +77,22 @@ function createInitialState<TExtra extends Record<string, unknown>>(
function serializeValue(v: unknown): string | null {
if (v === undefined || v === null) return null;
if (v instanceof Date) return v.toISOString();
if (Array.isArray(v)) return v.map((x) => x ?? '').join(','); // e.g., ids=1,2,3
if (v instanceof Object && (v as OptionType).value)
return String((v as OptionType).value);
if (Array.isArray(v))
return v
.map((x) => serializeValue(x))
.filter((x) => x !== null)
.join(',');
const t = typeof v;
if (t === 'string' || t === 'number' || t === 'boolean') return String(v);
try {
return JSON.stringify(v);
} catch {
@@ -70,32 +100,16 @@ function serializeValue(v: unknown): string | null {
}
}
// function shallowEqual(a: unknown, b: unknown): boolean {
// if (a === b) return true;
// if (!a || !b) return false;
// const ka = Object.keys(a);
// const kb = Object.keys(b);
// if (ka.length !== kb.length) return false;
// for (const k of ka) if (a[k] !== b[k]) return false;
// return true;
// }
function shallowEqual<T extends Record<string, unknown>>(
function shallowEqual<T extends TableFilterStateValue>(
a: T | undefined | null,
b: T | undefined | null
): boolean {
if (a === b) return true;
if (!a || !b) return false;
const ka = Object.keys(a) as (keyof T)[];
const kb = Object.keys(b) as (keyof T)[];
if (ka.length !== kb.length) return false;
for (const k of ka) if (a[k] !== b[k]) return false;
return true;
return JSON.stringify(a) === JSON.stringify(b);
}
export function useTableFilter<TExtra extends Record<string, unknown>>(
options?: UseTableFilterOptions<TExtra>
) {
export function useTableFilter<
TExtra extends Record<string, TableFilterStateValue>,
>(options?: UseTableFilterOptions<TExtra>) {
if (options?.persist && !options?.storeName) {
throw new Error(
'storeName is required if persist is true in useTableFilter!'
@@ -220,7 +234,9 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
);
const extras = useMemo(() => {
const stateWithExtras = state as TableFilterState<Record<string, unknown>>;
const stateWithExtras = state as TableFilterState<
Record<string, TableFilterStateValue>
>;
const rest = Object.fromEntries(
Object.entries(stateWithExtras).filter(
([key]) => key !== 'page' && key !== 'pageSize'
@@ -241,10 +257,8 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
/** Build URLSearchParams from current state */
const toSearchParams = useCallback(() => {
const params = new URLSearchParams();
const source = state as Record<string, unknown>;
const baseline = options?.omitDefaultsInUrl
? (defaults as Record<string, unknown>)
: null;
const source = state as Record<string, TableFilterStateValue>;
const baseline = options?.omitDefaultsInUrl ? defaults : null;
const excludedKeys = new Set<string>(
(options?.excludeKeysFromUrl as string[] | undefined) ?? []
);
@@ -255,13 +269,7 @@ export function useTableFilter<TExtra extends Record<string, unknown>>(
const value = source[key];
if (value === undefined || value === null) continue;
if (
baseline &&
shallowEqual(
value as Record<string, unknown>,
baseline[key] as Record<string, unknown>
)
) {
if (baseline && shallowEqual(value, baseline[key])) {
continue;
}