Skip to content

Commit a783c4d

Browse files
author
roshni73
committed
Add useFilterState
1 parent 6ca30d6 commit a783c4d

File tree

3 files changed

+421
-78
lines changed

3 files changed

+421
-78
lines changed

src/hooks/useDebouncedValue.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
useEffect,
3+
useState,
4+
} from 'react';
5+
6+
function useDebouncedValue<T>(
7+
input: T,
8+
debounceTime?: number,
9+
): T
10+
function useDebouncedValue<T, V>(
11+
input: T,
12+
debounceTime: number | undefined,
13+
transformer: (value: T) => V,
14+
): V
15+
function useDebouncedValue<T, V>(
16+
input: T,
17+
debounceTime?: number,
18+
transformer?: (value: T) => V,
19+
) {
20+
const [debounceValue, setDebouncedValue] = useState(
21+
() => (transformer ? transformer(input) : input),
22+
);
23+
useEffect(() => {
24+
const handler = setTimeout(() => {
25+
setDebouncedValue(transformer ? transformer(input) : input);
26+
}, debounceTime ?? 300);
27+
return () => {
28+
clearTimeout(handler);
29+
};
30+
}, [input, debounceTime, transformer]);
31+
return debounceValue;
32+
}
33+
34+
export default useDebouncedValue;

src/hooks/useFilterState.ts

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import {
2+
type SetStateAction,
3+
useCallback,
4+
useMemo,
5+
useReducer,
6+
} from 'react';
7+
import {
8+
isNotDefined,
9+
isTruthyString,
10+
} from '@togglecorp/fujs';
11+
import { EntriesAsList } from '@togglecorp/toggle-form';
12+
13+
import useDebouncedValue from '#hooks/useDebouncedValue';
14+
15+
type SortDirection = 'asc' | 'dsc';
16+
interface SortParameter {
17+
name: string;
18+
direction: SortDirection;
19+
}
20+
function getOrdering(sorting: SortParameter | undefined) {
21+
if (isNotDefined(sorting)) {
22+
return undefined;
23+
}
24+
if (sorting.direction === 'asc') {
25+
return sorting.name;
26+
}
27+
return `-${sorting.name}`;
28+
}
29+
30+
interface ResetFilterAction {
31+
type: 'reset-filter';
32+
}
33+
34+
interface SetFilterAction<FILTER extends object> {
35+
type: 'set-filter';
36+
value: SetStateAction<FILTER>;
37+
}
38+
39+
interface SetPageAction {
40+
type: 'set-page';
41+
value: number;
42+
}
43+
44+
interface SetOrderingAction {
45+
type: 'set-ordering'
46+
value: SetStateAction<SortParameter | undefined>;
47+
}
48+
49+
type FilterActions<FILTER extends object> = (
50+
ResetFilterAction
51+
| SetFilterAction<FILTER>
52+
| SetPageAction
53+
| SetOrderingAction
54+
);
55+
56+
interface FilterState<FILTER> {
57+
filter: FILTER,
58+
ordering: SortParameter | undefined,
59+
page: number,
60+
}
61+
62+
const defaultOrdering: SortParameter = {
63+
name: 'id',
64+
direction: 'dsc',
65+
};
66+
67+
function hasSomeDefinedValue(item: unknown) {
68+
if (isNotDefined(item)) {
69+
return false;
70+
}
71+
72+
if (typeof item === 'boolean') {
73+
return true;
74+
}
75+
76+
if (typeof item === 'number') {
77+
return !Number.isNaN(item);
78+
}
79+
80+
if (typeof item === 'string') {
81+
return isTruthyString(item.trim());
82+
}
83+
84+
if (Array.isArray(item)) {
85+
if (item.length === 0) {
86+
return false;
87+
}
88+
89+
return item.some(hasSomeDefinedValue);
90+
}
91+
92+
if (typeof item === 'object') {
93+
if (!item) {
94+
return false;
95+
}
96+
97+
return Object.values(item).some(hasSomeDefinedValue);
98+
}
99+
100+
return false;
101+
}
102+
103+
function useFilterState<FILTER extends object>(options: {
104+
filter: FILTER,
105+
ordering?: SortParameter | undefined,
106+
page?: number,
107+
pageSize?: number,
108+
debounceTime?: number,
109+
}) {
110+
const {
111+
filter,
112+
ordering = defaultOrdering,
113+
page = 1,
114+
pageSize = 10,
115+
debounceTime = 200,
116+
} = options;
117+
118+
type Reducer = (
119+
prevState: FilterState<FILTER>,
120+
action: FilterActions<FILTER>,
121+
) => FilterState<FILTER>;
122+
123+
const [state, dispatch] = useReducer<Reducer>(
124+
(prevState, action) => {
125+
if (action.type === 'reset-filter') {
126+
return {
127+
filter,
128+
ordering,
129+
page,
130+
};
131+
}
132+
if (action.type === 'set-filter') {
133+
return {
134+
...prevState,
135+
filter: typeof action.value === 'function'
136+
? action.value(prevState.filter)
137+
: action.value,
138+
page: 1,
139+
};
140+
}
141+
if (action.type === 'set-page') {
142+
return {
143+
...prevState,
144+
page: action.value,
145+
};
146+
}
147+
if (action.type === 'set-ordering') {
148+
return {
149+
...prevState,
150+
ordering: typeof action.value === 'function'
151+
? action.value(prevState.ordering)
152+
: action.value,
153+
page: 1,
154+
};
155+
}
156+
return prevState;
157+
},
158+
{
159+
filter,
160+
ordering,
161+
page,
162+
},
163+
);
164+
165+
const setFilter = useCallback(
166+
(value: SetStateAction<FILTER>) => {
167+
dispatch({
168+
type: 'set-filter',
169+
value,
170+
});
171+
},
172+
[],
173+
);
174+
175+
const resetFilter = useCallback(
176+
() => {
177+
dispatch({ type: 'reset-filter' });
178+
},
179+
[],
180+
);
181+
182+
const setFilterField = useCallback(
183+
(...args: EntriesAsList<FILTER>) => {
184+
const [val, key] = args;
185+
setFilter((oldFilterValue) => {
186+
const newFilterValue = {
187+
...oldFilterValue,
188+
[key]: val,
189+
};
190+
return newFilterValue;
191+
});
192+
},
193+
[setFilter],
194+
);
195+
196+
const setPage = useCallback(
197+
(value: number) => {
198+
dispatch({
199+
type: 'set-page',
200+
value,
201+
});
202+
},
203+
[],
204+
);
205+
const setOrdering = useCallback(
206+
(value: SetStateAction<SortParameter | undefined>) => {
207+
dispatch({
208+
type: 'set-ordering',
209+
value,
210+
});
211+
},
212+
[],
213+
);
214+
215+
const debouncedState = useDebouncedValue(state, debounceTime);
216+
217+
const sortState = useMemo(
218+
() => ({
219+
sorting: state.ordering,
220+
setSorting: setOrdering,
221+
}),
222+
[state.ordering, setOrdering],
223+
);
224+
225+
const filtered = useMemo(
226+
() => hasSomeDefinedValue(debouncedState.filter),
227+
[debouncedState.filter],
228+
);
229+
const rawFiltered = useMemo(
230+
() => hasSomeDefinedValue(state.filter),
231+
[state.filter],
232+
);
233+
234+
return {
235+
rawFilter: state.filter,
236+
rawFiltered,
237+
238+
filter: debouncedState.filter,
239+
filtered,
240+
setFilter,
241+
setFilterField,
242+
243+
resetFilter,
244+
245+
page: state.page,
246+
offset: pageSize * (debouncedState.page - 1),
247+
limit: pageSize,
248+
setPage,
249+
250+
rawOrdering: getOrdering(ordering),
251+
ordering: getOrdering(debouncedState.ordering),
252+
253+
sortState,
254+
};
255+
}
256+
257+
export default useFilterState;

0 commit comments

Comments
 (0)