@promoboxx/use-filter 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.js +29 -0
  2. package/dist/cjs/lib/buildDefaultFilterInfo.d.ts +5 -0
  3. package/dist/cjs/lib/buildDefaultFilterInfo.js +27 -0
  4. package/dist/cjs/lib/getOffsetFromPage.d.ts +3 -0
  5. package/dist/cjs/lib/getOffsetFromPage.js +8 -0
  6. package/dist/cjs/lib/getPageFromOffset.d.ts +3 -0
  7. package/dist/cjs/lib/getPageFromOffset.js +8 -0
  8. package/dist/cjs/lib/shallowEqual.d.ts +3 -0
  9. package/dist/cjs/lib/shallowEqual.js +24 -0
  10. package/dist/cjs/store/index.d.ts +14 -0
  11. package/dist/cjs/store/index.js +18 -0
  12. package/dist/cjs/store/localStorageStore.d.ts +5 -0
  13. package/dist/cjs/store/localStorageStore.js +33 -0
  14. package/dist/cjs/store/memoryStore.d.ts +5 -0
  15. package/dist/cjs/store/memoryStore.js +25 -0
  16. package/dist/cjs/store/reduxHelpers/createActions.d.ts +16 -0
  17. package/dist/cjs/store/reduxHelpers/createActions.js +16 -0
  18. package/dist/cjs/store/reduxHelpers/createReducer.d.ts +10 -0
  19. package/dist/cjs/store/reduxHelpers/createReducer.js +25 -0
  20. package/dist/cjs/store/reduxStore.d.ts +18 -0
  21. package/dist/cjs/store/reduxStore.js +63 -0
  22. package/dist/cjs/store/urlParamStore.d.ts +8 -0
  23. package/dist/cjs/store/urlParamStore.js +68 -0
  24. package/dist/cjs/useFilter.d.ts +106 -0
  25. package/dist/cjs/useFilter.js +251 -0
  26. package/dist/cjs/useSimpleFilter.d.ts +89 -0
  27. package/dist/cjs/useSimpleFilter.js +198 -0
  28. package/dist/esm/lib/buildDefaultFilterInfo.d.mts +6 -0
  29. package/dist/esm/lib/buildDefaultFilterInfo.mjs +27 -0
  30. package/dist/esm/lib/getOffsetFromPage.d.mts +4 -0
  31. package/dist/esm/lib/getOffsetFromPage.mjs +7 -0
  32. package/dist/esm/lib/getPageFromOffset.d.mts +4 -0
  33. package/dist/esm/lib/getPageFromOffset.mjs +7 -0
  34. package/dist/esm/lib/shallowEqual.d.mts +4 -0
  35. package/dist/esm/lib/shallowEqual.mjs +23 -0
  36. package/dist/esm/store/index.d.mts +14 -0
  37. package/dist/esm/store/index.mjs +15 -0
  38. package/dist/esm/store/localStorageStore.d.mts +6 -0
  39. package/dist/esm/store/localStorageStore.mjs +32 -0
  40. package/dist/esm/store/memoryStore.d.mts +6 -0
  41. package/dist/esm/store/memoryStore.mjs +24 -0
  42. package/dist/esm/store/reduxHelpers/createActions.d.mts +16 -0
  43. package/dist/esm/store/reduxHelpers/createActions.mjs +15 -0
  44. package/dist/esm/store/reduxHelpers/createReducer.d.mts +11 -0
  45. package/dist/esm/store/reduxHelpers/createReducer.mjs +24 -0
  46. package/dist/esm/store/reduxStore.d.mts +18 -0
  47. package/dist/esm/store/reduxStore.mjs +61 -0
  48. package/dist/esm/store/urlParamStore.d.mts +8 -0
  49. package/dist/esm/store/urlParamStore.mjs +63 -0
  50. package/dist/esm/useFilter.d.mts +106 -0
  51. package/dist/esm/useFilter.mjs +251 -0
  52. package/dist/esm/useSimpleFilter.d.mts +89 -0
  53. package/dist/esm/useSimpleFilter.mjs +198 -0
  54. package/package.json +1 -1
  55. package/.github/workflows/main.yml +0 -38
  56. package/.vscode/settings.json +0 -3
  57. package/CHANGELOG.md +0 -185
  58. package/Makefile +0 -25
  59. package/eslint.config.js +0 -30
  60. package/mise.toml +0 -3
  61. package/prettier.config.js +0 -3
  62. package/src/lib/buildDefaultFilterInfo.ts +0 -36
  63. package/src/lib/getOffsetFromPage.ts +0 -5
  64. package/src/lib/getPageFromOffset.ts +0 -5
  65. package/src/lib/shallowEqual.test.ts +0 -71
  66. package/src/lib/shallowEqual.ts +0 -26
  67. package/src/store/index.ts +0 -30
  68. package/src/store/localStorageStore.ts +0 -36
  69. package/src/store/memoryStore.ts +0 -27
  70. package/src/store/reduxHelpers/createActions.test.ts +0 -32
  71. package/src/store/reduxHelpers/createActions.ts +0 -56
  72. package/src/store/reduxHelpers/createReducer.test.ts +0 -65
  73. package/src/store/reduxHelpers/createReducer.ts +0 -47
  74. package/src/store/reduxStore.ts +0 -78
  75. package/src/store/urlParamStore.test.ts +0 -131
  76. package/src/store/urlParamStore.ts +0 -85
  77. package/src/useFilter.test.tsx +0 -822
  78. package/src/useFilter.ts +0 -524
  79. package/src/useSimpleFilter.test.tsx +0 -676
  80. package/src/useSimpleFilter.ts +0 -397
  81. package/src/vitest-env.d.ts +0 -1
  82. package/tsconfig.json +0 -76
  83. package/tsdown.config.ts +0 -30
  84. package/vite.config.ts +0 -9
@@ -0,0 +1,32 @@
1
+ //#region src/store/localStorageStore.ts
2
+ const prefix = "useFilter";
3
+ const localStorageStore = {
4
+ getFilter(namespace) {
5
+ const item = localStorage.getItem(`${prefix}/${namespace}/filter`);
6
+ if (item) {
7
+ return JSON.parse(item);
8
+ }
9
+ },
10
+ saveFilter(namespace, filter) {
11
+ localStorage.setItem(`${prefix}/${namespace}/filter`, JSON.stringify(filter));
12
+ },
13
+ getData(namespace) {
14
+ const item = localStorage.getItem(`${prefix}/${namespace}/data`);
15
+ if (item) {
16
+ return JSON.parse(item);
17
+ }
18
+ },
19
+ saveData(namespace, data) {
20
+ localStorage.setItem(`${prefix}/${namespace}/data`, JSON.stringify(data));
21
+ },
22
+ clear() {
23
+ for (const key in localStorage) {
24
+ if (key.startsWith(prefix)) {
25
+ localStorage.removeItem(key);
26
+ }
27
+ }
28
+ }
29
+ };
30
+
31
+ //#endregion
32
+ export { localStorageStore as default };
@@ -0,0 +1,6 @@
1
+ import { FilterStore } from "./index.mjs";
2
+
3
+ //#region src/store/memoryStore.d.ts
4
+ declare const memoryStore: FilterStore;
5
+ //#endregion
6
+ export { memoryStore as default };
@@ -0,0 +1,24 @@
1
+ //#region src/store/memoryStore.ts
2
+ let cachedFilters = {};
3
+ let cachedData = {};
4
+ const memoryStore = {
5
+ getFilter(namespace) {
6
+ return cachedFilters[namespace];
7
+ },
8
+ saveFilter(namespace, filter) {
9
+ cachedFilters[namespace] = filter;
10
+ },
11
+ getData(namespace) {
12
+ return cachedData[namespace];
13
+ },
14
+ saveData(namespace, data) {
15
+ cachedData[namespace] = data;
16
+ },
17
+ clear() {
18
+ cachedFilters = {};
19
+ cachedData = {};
20
+ }
21
+ };
22
+
23
+ //#endregion
24
+ export { memoryStore as default };
@@ -0,0 +1,16 @@
1
+ //#region src/store/reduxHelpers/createActions.d.ts
2
+ interface CreateActions {
3
+ <Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap>(namespace: Namespace, actions: PayloadCreatorMap): ReduxActionCreatorMap<Namespace, PayloadCreatorMap>;
4
+ }
5
+ type ReduxPayloadCreatorMap = Record<string, (...args: any[]) => any>;
6
+ type ReduxActionCreatorMap<Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap> = { [Type in keyof PayloadCreatorMap]: Type extends string ? ReduxActionCreator<Namespace, Type, PayloadCreatorMap[Type]> : undefined };
7
+ interface ReduxActionCreator<Namespace extends string = string, Type extends string = string, Fn extends (...args: any[]) => any = (...args: any[]) => any> {
8
+ (...args: Parameters<Fn>): {
9
+ type: `${Namespace}/${Type}`;
10
+ payload: ReturnType<Fn>;
11
+ };
12
+ actionType: `${Namespace}/${Type}`;
13
+ }
14
+ declare const createActions: CreateActions;
15
+ //#endregion
16
+ export { ReduxActionCreator, ReduxActionCreatorMap, createActions as default };
@@ -0,0 +1,15 @@
1
+ //#region src/store/reduxHelpers/createActions.ts
2
+ const createActions = (namespace, payloadCreatorMap) => {
3
+ const actionCreators = {};
4
+ for (const key in payloadCreatorMap) {
5
+ actionCreators[key] = ((...args) => ({
6
+ type: `${namespace}/${key}`,
7
+ payload: payloadCreatorMap[key](...args)
8
+ }));
9
+ actionCreators[key].actionType = `${namespace}/${key}`;
10
+ }
11
+ return actionCreators;
12
+ };
13
+
14
+ //#endregion
15
+ export { createActions as default };
@@ -0,0 +1,11 @@
1
+ import { ReduxActionCreator } from "./createActions.mjs";
2
+ import { Action } from "redux";
3
+
4
+ //#region src/store/reduxHelpers/createReducer.d.ts
5
+ interface ReduxReducerBuilder<State> {
6
+ addHandler: <ActionCreator extends ReduxActionCreator>(actionCreator: ActionCreator, handler: (state: State, action: ReturnType<ActionCreator>) => State) => ReduxReducerBuilder<State>;
7
+ addCase: <HandlerAction extends Action>(actionType: string, handler: (state: State, action: HandlerAction) => State) => ReduxReducerBuilder<State>;
8
+ }
9
+ declare function createReducer<State>(initialState: State, builderFn: (builder: ReduxReducerBuilder<State>) => void): (state: State | undefined, action: Action) => State;
10
+ //#endregion
11
+ export { createReducer as default };
@@ -0,0 +1,24 @@
1
+ //#region src/store/reduxHelpers/createReducer.ts
2
+ function createReducer(initialState, builderFn) {
3
+ const handlers = {};
4
+ const builder = {
5
+ addHandler: (actionCreator, handler) => {
6
+ handlers[actionCreator.actionType] = handler;
7
+ return builder;
8
+ },
9
+ addCase: (actionType, handler) => {
10
+ handlers[actionType] = handler;
11
+ return builder;
12
+ }
13
+ };
14
+ builderFn(builder);
15
+ return (state = initialState, action) => {
16
+ if (handlers[action.type]) {
17
+ return handlers[action.type](state, action);
18
+ }
19
+ return state;
20
+ };
21
+ }
22
+
23
+ //#endregion
24
+ export { createReducer as default };
@@ -0,0 +1,18 @@
1
+ import { FilterStore } from "./index.mjs";
2
+ import { FilterInfo } from "../useFilter.mjs";
3
+ import { Store } from "redux";
4
+
5
+ //#region src/store/reduxStore.d.ts
6
+ interface CreateReduxStoreConfig {
7
+ store: Store<{
8
+ useFilter: UseFilterReduxState;
9
+ }>;
10
+ }
11
+ declare function createReduxStore(config: CreateReduxStoreConfig): FilterStore;
12
+ interface UseFilterReduxState {
13
+ data: Record<string, any>;
14
+ filters: Record<string, FilterInfo<any>>;
15
+ }
16
+ declare const useFilterReduxReducer: (state: UseFilterReduxState | undefined, action: import("redux").Action) => UseFilterReduxState;
17
+ //#endregion
18
+ export { createReduxStore, useFilterReduxReducer };
@@ -0,0 +1,61 @@
1
+ import createActions from "./reduxHelpers/createActions.mjs";
2
+ import createReducer from "./reduxHelpers/createReducer.mjs";
3
+
4
+ //#region src/store/reduxStore.ts
5
+ function createReduxStore(config) {
6
+ const reduxStoreStore = {
7
+ getFilter: (namespace) => {
8
+ return config.store.getState().useFilter.filters[namespace];
9
+ },
10
+ saveFilter: (namespace, filter) => {
11
+ config.store.dispatch(actions.saveFilter(namespace, filter));
12
+ },
13
+ getData: (namespace) => {
14
+ return config.store.getState().useFilter.data[namespace];
15
+ },
16
+ saveData: (namespace, data) => {
17
+ config.store.dispatch(actions.saveData(namespace, data));
18
+ },
19
+ clear: () => {
20
+ config.store.dispatch(actions.clear());
21
+ }
22
+ };
23
+ return reduxStoreStore;
24
+ }
25
+ const actions = createActions("useFilter", {
26
+ saveData: (namespace, data) => ({
27
+ namespace,
28
+ data
29
+ }),
30
+ saveFilter: (namespace, filter) => ({
31
+ namespace,
32
+ filter
33
+ }),
34
+ clear: () => undefined
35
+ });
36
+ const initialState = {
37
+ data: {},
38
+ filters: {}
39
+ };
40
+ const useFilterReduxReducer = createReducer(initialState, (builder) => {
41
+ builder.addHandler(actions.saveFilter, (state, action) => ({
42
+ ...state,
43
+ filters: {
44
+ ...state.filters,
45
+ [action.payload.namespace]: action.payload.filter
46
+ }
47
+ })).addHandler(actions.saveData, (state, action) => ({
48
+ ...state,
49
+ data: {
50
+ ...state.data,
51
+ [action.payload.namespace]: action.payload.data
52
+ }
53
+ })).addHandler(actions.clear, (state) => ({
54
+ ...state,
55
+ filters: {},
56
+ data: {}
57
+ }));
58
+ });
59
+
60
+ //#endregion
61
+ export { createReduxStore, useFilterReduxReducer };
@@ -0,0 +1,8 @@
1
+ import { FilterStore } from "./index.mjs";
2
+
3
+ //#region src/store/urlParamStore.d.ts
4
+ declare const createUrlParamStore: () => FilterStore;
5
+ declare function replaceQueryParams(newParams: string): void;
6
+ declare function naivelyParseExistingParams(): any;
7
+ //#endregion
8
+ export { createUrlParamStore, naivelyParseExistingParams, replaceQueryParams };
@@ -0,0 +1,63 @@
1
+ import qs from "qs";
2
+
3
+ //#region src/store/urlParamStore.ts
4
+ const createUrlParamStore = () => {
5
+ const urlParamStore = {
6
+ getFilter(namespace) {
7
+ const parsed = naivelyParseExistingParams();
8
+ const parsedInfo = parsed[`info.${namespace}`];
9
+ const parsedFilter = parsed[`filter.${namespace}`];
10
+ if (parsedInfo || parsedFilter) {
11
+ const { page, offset, pageSize, ...rest } = parsedInfo || {};
12
+ return {
13
+ ...{
14
+ page: 1 / page ? +page : undefined,
15
+ pageSize: 1 / pageSize ? +pageSize : undefined,
16
+ offset: 1 / offset ? +offset : undefined,
17
+ ...rest
18
+ },
19
+ filter: parsedFilter
20
+ };
21
+ }
22
+ },
23
+ saveFilter(namespace, filterInfo) {
24
+ const parsed = naivelyParseExistingParams();
25
+ const clonedInfo = { ...filterInfo };
26
+ const clonedFilter = filterInfo.filter;
27
+ delete clonedInfo.filter;
28
+ delete clonedInfo.lastRefreshAt;
29
+ delete clonedInfo.shouldRunImmediately;
30
+ delete clonedInfo.totalResults;
31
+ delete clonedInfo.totalPages;
32
+ delete clonedInfo.nextCursor;
33
+ parsed[`filter.${namespace}`] = clonedFilter;
34
+ parsed[`info.${namespace}`] = clonedInfo;
35
+ replaceQueryParams(qs.stringify(parsed));
36
+ },
37
+ clear() {
38
+ const parsed = naivelyParseExistingParams();
39
+ for (const key in parsed) {
40
+ if (key.startsWith("filter.") || key.startsWith("info.")) {
41
+ delete parsed[key];
42
+ }
43
+ }
44
+ replaceQueryParams(qs.stringify(parsed));
45
+ },
46
+ getData() {
47
+ return undefined;
48
+ },
49
+ saveData() {}
50
+ };
51
+ return urlParamStore;
52
+ };
53
+ function replaceQueryParams(newParams) {
54
+ const nextUrl = new URL(window.location.toString());
55
+ nextUrl.search = newParams;
56
+ window.history.replaceState(undefined, "", nextUrl.toString());
57
+ }
58
+ function naivelyParseExistingParams() {
59
+ return qs.parse(window.location.search.substring(1));
60
+ }
61
+
62
+ //#endregion
63
+ export { createUrlParamStore, naivelyParseExistingParams, replaceQueryParams };
@@ -0,0 +1,106 @@
1
+ import { FilterStore } from "./store/index.mjs";
2
+
3
+ //#region src/useFilter.d.ts
4
+ type MaybePromise<T> = T | Promise<T>;
5
+ interface UseFilterOptions<TFilter extends Record<string, unknown>, TResult> {
6
+ /**
7
+ * Default values for your filter. When calling `.reset` your filter will be
8
+ * set to this.
9
+ * Changing these values does not cause a call to happen.
10
+ */
11
+ defaultFilterInfo?: Partial<FilterInfo<TFilter>>;
12
+ /**
13
+ * Enable this if you are not using the data caching of this hook.
14
+ */
15
+ shouldForceRunOnMount?: boolean;
16
+ /**
17
+ * Called whenever the filter changes.
18
+ */
19
+ onChange: (filterInfo: FilterInfo<TFilter>, reason: UseFilterUpdateReason) => MaybePromise<UseFilterOnChangeResult<TFilter, TResult>>;
20
+ /**
21
+ * In case you want to change the default debounce duration.
22
+ */
23
+ debounceDuration?: number;
24
+ onBeforeSaveFilter?: (filterInfo: FilterInfo<TFilter>) => FilterInfo<TFilter>;
25
+ store?: FilterStore;
26
+ }
27
+ interface FilterApi<TFilter extends Record<string, unknown>, TResult> {
28
+ /**
29
+ * Whether the system is debouncing or waiting for your `onChange` to finish.
30
+ */
31
+ isLoading: boolean;
32
+ /**
33
+ * Any cached data for this filter.
34
+ */
35
+ data: TResult | null | undefined;
36
+ /**
37
+ * Access to the filter and its metadata.
38
+ */
39
+ filterInfo: FilterInfo<TFilter>;
40
+ /**
41
+ * Whether the filter existed in the system when the component mounted.
42
+ * @deprecated
43
+ */
44
+ doesFilterExist: boolean;
45
+ /**
46
+ * Why the last update happened.
47
+ */
48
+ updateReason: UseFilterUpdateReason;
49
+ /**
50
+ * Update one or more values in your filter.
51
+ */
52
+ updateFilter: (filter: Partial<TFilter>, shouldRunImmediately?: boolean) => void;
53
+ /**
54
+ * Resets the filter back to `defaultFilterInfo`.
55
+ */
56
+ resetFilter: (shouldRunImmediately?: boolean) => void;
57
+ /**
58
+ * Changes the offset. Will update `page`.
59
+ */
60
+ setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void;
61
+ /**
62
+ * Changes the page. Will update `offset`.
63
+ */
64
+ setPage: (page: number | string, shouldRunImmediately?: boolean) => void;
65
+ /**
66
+ * Changes the page size.
67
+ */
68
+ setPageSize: (pageSize: number | string, shouldRunImmediately?: boolean) => void;
69
+ /**
70
+ * Change the sort method.
71
+ */
72
+ setSort: (sort: string | undefined, shouldRunImmediately?: boolean) => void;
73
+ /**
74
+ * Changes the cursor.
75
+ */
76
+ setCursor: (cursor: string | null | undefined, shouldRunImmediately?: boolean) => void;
77
+ /**
78
+ * Forces a refresh of the filter.
79
+ */
80
+ forceRefresh: (shouldRunImmediately?: boolean) => void;
81
+ }
82
+ type UseFilterUpdateReason = 'initial' | 'filter' | 'pagination';
83
+ declare function useFilter<TFilter extends Record<string, unknown>, TResult>(namespace: string, options: UseFilterOptions<TFilter, TResult>): FilterApi<TFilter, TResult>;
84
+ interface FilterInfo<TFilter extends Record<string, unknown>> {
85
+ filter: TFilter;
86
+ offset: number;
87
+ page: number;
88
+ sort?: string;
89
+ pageSize: number;
90
+ lastRefreshAt: number;
91
+ totalResults: number;
92
+ totalPages: number;
93
+ shouldRunImmediately: boolean;
94
+ cursor?: string | null;
95
+ nextCursor?: string | null;
96
+ }
97
+ type UseFilterOnChangeResult<TFilter extends Record<string, unknown>, TResult> = void | {
98
+ filterInfo?: Partial<Omit<FilterInfo<TFilter>, 'totalResults' | 'offset' | 'pageSize'> & {
99
+ totalResults: string | number;
100
+ offset: string | number;
101
+ pageSize: string | number;
102
+ }>;
103
+ data?: TResult;
104
+ };
105
+ //#endregion
106
+ export { FilterApi, FilterInfo, UseFilterUpdateReason, useFilter as default };
@@ -0,0 +1,251 @@
1
+ import getOffsetFromPage from "./lib/getOffsetFromPage.mjs";
2
+ import getPageFromOffset from "./lib/getPageFromOffset.mjs";
3
+ import buildDefaultFilterInfo from "./lib/buildDefaultFilterInfo.mjs";
4
+ import shallowEqual from "./lib/shallowEqual.mjs";
5
+ import { getFilterStore } from "./store/index.mjs";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+
8
+ //#region src/useFilter.ts
9
+ function useFilter(namespace, options) {
10
+ const ctxRefValue = {
11
+ namespace,
12
+ onChange: options.onChange,
13
+ debounceDuration: options.debounceDuration,
14
+ defaultFilterInfo: options.defaultFilterInfo,
15
+ onBeforeSaveFilter: options.onBeforeSaveFilter,
16
+ store: options.store
17
+ };
18
+ const ctxRef = useRef(ctxRefValue);
19
+ useEffect(() => {
20
+ ctxRef.current = ctxRefValue;
21
+ });
22
+ const [filterInfo, setFilterInfoState] = useState(() => {
23
+ const fromStore = getFilterStore(ctxRef.current.store).getFilter(namespace);
24
+ if (fromStore) {
25
+ const filterInfo = buildDefaultFilterInfo({
26
+ ...fromStore,
27
+ shouldRunImmediately: true,
28
+ filter: {
29
+ ...options.defaultFilterInfo?.filter,
30
+ ...fromStore.filter
31
+ }
32
+ });
33
+ return filterInfo;
34
+ }
35
+ });
36
+ const [data, setData] = useState(() => getFilterStore(ctxRef.current.store).getData(namespace));
37
+ const [isLoading, setIsLoading] = useState(!filterInfo);
38
+ const doesFilterExist = useRef(!!filterInfo);
39
+ const lastRefreshAtRef = useRef(options.shouldForceRunOnMount ? -1 : filterInfo ? filterInfo.lastRefreshAt : -1);
40
+ const updateReasonRef = useRef("initial");
41
+ useEffect(() => {
42
+ setIsLoading(true);
43
+ if (!filterInfo) {
44
+ setFilterInfoState({
45
+ ...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
46
+ shouldRunImmediately: true
47
+ });
48
+ return;
49
+ }
50
+ if (lastRefreshAtRef.current === filterInfo.lastRefreshAt) {
51
+ setIsLoading(false);
52
+ return;
53
+ }
54
+ const makeRequest = () => {
55
+ lastRefreshAtRef.current = filterInfo.lastRefreshAt;
56
+ const response = ctxRef.current.onChange(filterInfo, updateReasonRef.current);
57
+ getFilterStore(ctxRef.current.store).saveFilter(namespace, ctxRef.current.onBeforeSaveFilter ? ctxRef.current.onBeforeSaveFilter(filterInfo) : filterInfo);
58
+ if (!response) {
59
+ setIsLoading(false);
60
+ return;
61
+ }
62
+ function handleResponse(response) {
63
+ if (!response) {
64
+ return;
65
+ }
66
+ if (response.filterInfo) {
67
+ setFilterInfoState((previous) => {
68
+ invariant(!!response.filterInfo, "");
69
+ invariant(previous != null, "handleResponse called without filterInfo");
70
+ const extra = {};
71
+ const { page, offset, totalResults, pageSize, nextCursor, ...filterInfoFromResponse } = response.filterInfo;
72
+ const pageSizeNumber = Number(pageSize || previous.pageSize);
73
+ if (page != null && offset == null) {
74
+ extra.offset = getOffsetFromPage(page, pageSizeNumber);
75
+ extra.page = page;
76
+ } else if (page == null && offset != null) {
77
+ const offsetNumber = Number(offset);
78
+ extra.offset = offsetNumber;
79
+ extra.page = getPageFromOffset(offsetNumber, pageSizeNumber);
80
+ }
81
+ if ("totalResults" in response.filterInfo) {
82
+ extra.totalResults = Number(totalResults || 0);
83
+ extra.totalPages = Math.ceil(extra.totalResults / pageSizeNumber);
84
+ }
85
+ if ("nextCursor" in response.filterInfo) {
86
+ extra.nextCursor = nextCursor;
87
+ }
88
+ return {
89
+ ...previous,
90
+ ...filterInfoFromResponse,
91
+ ...extra
92
+ };
93
+ });
94
+ }
95
+ if (response.data) {
96
+ setData(response.data);
97
+ getFilterStore(ctxRef.current.store).saveData(namespace, response.data);
98
+ }
99
+ }
100
+ if (isPromise(response)) {
101
+ response.then(handleResponse).finally(() => setIsLoading(false));
102
+ } else {
103
+ handleResponse(response);
104
+ setIsLoading(false);
105
+ }
106
+ };
107
+ let timeout = undefined;
108
+ if (filterInfo.shouldRunImmediately) {
109
+ makeRequest();
110
+ } else {
111
+ timeout = setTimeout(makeRequest, ctxRef.current.debounceDuration != null ? ctxRef.current.debounceDuration : 500);
112
+ }
113
+ return () => {
114
+ if (timeout) {
115
+ clearTimeout(timeout);
116
+ }
117
+ };
118
+ }, [filterInfo, namespace]);
119
+ const api = {
120
+ isLoading,
121
+ data,
122
+ doesFilterExist: doesFilterExist.current,
123
+ filterInfo: filterInfo || buildDefaultFilterInfo(options.defaultFilterInfo),
124
+ updateReason: updateReasonRef.current,
125
+ updateFilter: useCallback((filter, shouldRunImmediately = false) => {
126
+ updateReasonRef.current = "filter";
127
+ setFilterInfoState((previous) => {
128
+ invariant(previous != null, "updateFilter called without filterInfo");
129
+ const nextFilter = {
130
+ ...previous.filter,
131
+ ...filter
132
+ };
133
+ if (shallowEqual(previous.filter, nextFilter)) {
134
+ return previous;
135
+ }
136
+ return {
137
+ ...previous,
138
+ offset: 0,
139
+ page: 1,
140
+ totalResults: 1,
141
+ totalPages: 1,
142
+ filter: nextFilter,
143
+ lastRefreshAt: new Date().getTime(),
144
+ cursor: undefined,
145
+ nextCursor: undefined,
146
+ shouldRunImmediately
147
+ };
148
+ });
149
+ }, []),
150
+ resetFilter: useCallback((shouldRunImmediately = false) => {
151
+ updateReasonRef.current = "filter";
152
+ setFilterInfoState({
153
+ ...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
154
+ lastRefreshAt: new Date().getTime(),
155
+ shouldRunImmediately
156
+ });
157
+ }, []),
158
+ setOffset: useCallback((offset, shouldRunImmediately = false) => {
159
+ updateReasonRef.current = "pagination";
160
+ setFilterInfoState((previous) => {
161
+ invariant(previous != null, "setOffset called without filterInfo");
162
+ const offsetNumber = Number(offset);
163
+ return {
164
+ ...previous,
165
+ offset: offsetNumber,
166
+ page: getPageFromOffset(offsetNumber, previous.pageSize),
167
+ lastRefreshAt: new Date().getTime(),
168
+ shouldRunImmediately
169
+ };
170
+ });
171
+ }, []),
172
+ setPage: useCallback((page, shouldRunImmediately = false) => {
173
+ updateReasonRef.current = "pagination";
174
+ setFilterInfoState((previous) => {
175
+ invariant(previous != null, "setPage called without filterInfo");
176
+ const pageNumber = Number(page);
177
+ return {
178
+ ...previous,
179
+ offset: getOffsetFromPage(pageNumber, previous.pageSize),
180
+ page: pageNumber,
181
+ lastRefreshAt: new Date().getTime(),
182
+ shouldRunImmediately
183
+ };
184
+ });
185
+ }, []),
186
+ setPageSize: useCallback((pageSize, shouldRunImmediately = false) => {
187
+ updateReasonRef.current = "pagination";
188
+ setFilterInfoState((previous) => {
189
+ invariant(previous != null, "setPageSize called without filterInfo");
190
+ const pageSizeNumber = Number(pageSize);
191
+ const page = getPageFromOffset(previous.offset, pageSizeNumber);
192
+ const offset = getOffsetFromPage(page, pageSizeNumber);
193
+ return {
194
+ ...previous,
195
+ pageSize: pageSizeNumber,
196
+ offset,
197
+ page,
198
+ lastRefreshAt: new Date().getTime(),
199
+ shouldRunImmediately
200
+ };
201
+ });
202
+ }, []),
203
+ setSort: useCallback((sort, shouldRunImmediately = false) => {
204
+ updateReasonRef.current = "filter";
205
+ setFilterInfoState((previous) => {
206
+ invariant(previous != null, "setSort called without filterInfo");
207
+ return {
208
+ ...previous,
209
+ sort,
210
+ lastRefreshAt: new Date().getTime(),
211
+ shouldRunImmediately
212
+ };
213
+ });
214
+ }, []),
215
+ setCursor: useCallback((cursor, shouldRunImmediately = false) => {
216
+ setFilterInfoState((previous) => {
217
+ updateReasonRef.current = "pagination";
218
+ invariant(previous != null, "setCursor called without filterInfo");
219
+ return {
220
+ ...previous,
221
+ cursor,
222
+ lastRefreshAt: new Date().getTime(),
223
+ shouldRunImmediately
224
+ };
225
+ });
226
+ }, []),
227
+ forceRefresh: useCallback((shouldRunImmediately = false) => {
228
+ updateReasonRef.current = "filter";
229
+ setFilterInfoState((previous) => {
230
+ invariant(previous != null, "forceRefresh called without filterInfo");
231
+ return {
232
+ ...previous,
233
+ lastRefreshAt: new Date().getTime(),
234
+ shouldRunImmediately
235
+ };
236
+ });
237
+ }, [])
238
+ };
239
+ return api;
240
+ }
241
+ function invariant(input, message) {
242
+ if (!input) {
243
+ throw new Error(`Invariant error: ${message}`);
244
+ }
245
+ }
246
+ function isPromise(t) {
247
+ return !!t?.then;
248
+ }
249
+
250
+ //#endregion
251
+ export { useFilter as default };