@promoboxx/use-filter 1.8.0 → 1.8.2

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.
package/README.md ADDED
@@ -0,0 +1,413 @@
1
+ # @promoboxx/use-filter
2
+
3
+ A React hook to easily build filter views, with features like:
4
+
5
+ - persistence
6
+ - debouncing
7
+ - cursor based pagination
8
+ - offset / page based pagination
9
+
10
+ [Demo](https://stackblitz.com/edit/github-8ssor8)
11
+
12
+ ## What's Included
13
+
14
+ The filter system providers you with a few things:
15
+
16
+ - `filter`: A key/value store for arbitrary filter values
17
+ - `filterInfo`: Metadata around your filter with the current page, number of results, total pages, cursor, etc
18
+ - `filterApi`: What is returned from the hook, complete with `filterInfo` and helpers to update values in your `filter`, change the page, etc.
19
+
20
+ ## Usage
21
+
22
+ There are two hooks provided. The default, "advanced", is more controlled, where you provide an `onChange` function which is called by the hook whenever you manipulate the filter.
23
+
24
+ The alternative, "simple", works similarly but gives you both `filterInfo` and a `debouncedFilterInfo` for you to use in effects.
25
+
26
+ ### Advanced Example
27
+
28
+ ```tsx
29
+ const { filterInfo, updateFilter, setPage, resetFilter, isLoading } = useFilter(
30
+ // Since this deals with caching, a namespace is required.
31
+ 'advanced',
32
+ {
33
+ // Disable this if you are using the hook's data caching.
34
+ shouldForceRunOnMount: true,
35
+
36
+ defaultFilterInfo: {
37
+ filter: {
38
+ name: '',
39
+ },
40
+ pageSize: 20,
41
+ },
42
+
43
+ // Called by the hook whenever the filter changes.
44
+ async onChange(
45
+ filterInfo,
46
+ // If this is called as a result of the filter changing, the reason will
47
+ // be 'filter', If it's due to the page changing, it will be 'pagination'.
48
+ updateReason,
49
+ ) {
50
+ const { data } = await runQuery({
51
+ name: filterInfo.filter.name,
52
+ page: filterInfo.page,
53
+ })
54
+
55
+ // Here we are telling the filter system how many results there are. Since
56
+ // it knows how many we want to display per-page, it can now figure out
57
+ // how many pages there are in total.
58
+ return {
59
+ filterInfo: {
60
+ totalResults: data?.locations?.info.count,
61
+ },
62
+ }
63
+ },
64
+ },
65
+ )
66
+
67
+ return (
68
+ <>
69
+ <input
70
+ type="text"
71
+ value={filterInfo.filter.name}
72
+ onChange={(event) => {
73
+ updateFilter({ name: event.target.value })
74
+ }}
75
+ />
76
+
77
+ <button onClick={resetFilter}>Reset</button>
78
+
79
+ {isLoading ? <CircularProgress /> : undefined}
80
+
81
+ {/* Hypothetical list of results would be here */}
82
+
83
+ <Pagination
84
+ totalPages={filterInfo.totalPages}
85
+ page={filterInfo.page}
86
+ onPageChange={(page) => {
87
+ setPage(page)
88
+ }}
89
+ />
90
+ </>
91
+ )
92
+ ```
93
+
94
+ ### Simple Example
95
+
96
+ ```tsx
97
+ const {
98
+ filterInfo,
99
+ debouncedFilterInfo,
100
+ updateFilter,
101
+ pagingInfo,
102
+ setPage,
103
+ resetFilter,
104
+ isLoading,
105
+ } = useSimpleFilter('simple', {
106
+ defaultFilterInfo: {
107
+ filter: {
108
+ name: '',
109
+ },
110
+ pageSize: 20,
111
+ },
112
+ })
113
+
114
+ // Hypothetical fetch / query / graphql hook.
115
+ const { data, fetch } = useQuery()
116
+
117
+ useEffect(() => {
118
+ fetch({
119
+ name: debouncedFilterInfo.filter.name,
120
+ page: debouncedFilterInfo.page,
121
+ })
122
+ }, [debouncedFilterInfo])
123
+
124
+ // Since the simple mode doesn't know when we're done doing our work, it
125
+ // provides a helper to get us pagination info.
126
+ const { totalPages } = pagingInfo(data?.count)
127
+
128
+ return (
129
+ <>
130
+ <input
131
+ type="text"
132
+ value={filterInfo.filter.name}
133
+ onChange={(event) => {
134
+ updateFilter({ name: event.target.value })
135
+ }}
136
+ />
137
+
138
+ <button onClick={resetFilter}>Reset</button>
139
+
140
+ {isLoading ? <CircularProgress /> : undefined}
141
+
142
+ {/* Hypothetical list of results would be here */}
143
+
144
+ <Pagination
145
+ totalPages={totalPages}
146
+ page={filterInfo.page}
147
+ onPageChange={(page) => {
148
+ setPage(page)
149
+ }}
150
+ />
151
+ </>
152
+ )
153
+ ```
154
+
155
+ ## API
156
+
157
+ ### `useFilter(options)`
158
+
159
+ ```tsx
160
+ interface UseFilterOptions<TFilter, TResult> {
161
+ /**
162
+ * Default values for your filter. When calling `.reset` your filter will be
163
+ * set to this.
164
+ * Changing these values does not cause a call to happen.
165
+ */
166
+ defaultFilterInfo?: Partial<FilterInfo<TFilter>>
167
+
168
+ /**
169
+ * Enable this if you are not using the data caching of this hook.
170
+ */
171
+ shouldForceRunOnMount?: boolean
172
+
173
+ /**
174
+ * Called whenever the filter changes.
175
+ */
176
+ onChange: (
177
+ filterInfo: FilterInfo<TFilter>,
178
+ reason: UseFilterUpdateReason,
179
+ ) => MaybePromise<UseFilterOnChangeResult<TFilter, TResult>>
180
+
181
+ /**
182
+ * In case you want to change the default debounce duration.
183
+ */
184
+ debounceDuration?: number
185
+ }
186
+
187
+ export interface FilterApi<TFilter, TResult> {
188
+ /**
189
+ * Whether the system is debouncing or waiting for your `onChange` to finish.
190
+ */
191
+ isLoading: boolean
192
+ /**
193
+ * Any cached data for this filter.
194
+ */
195
+ data: TResult | null | undefined
196
+ /**
197
+ * Access to the filter and its metadata.
198
+ */
199
+ filterInfo: FilterInfo<TFilter>
200
+
201
+ /**
202
+ * Whether the filter existed in the system when the component mounted.
203
+ * @deprecated
204
+ */
205
+ doesFilterExist: boolean
206
+ /**
207
+ * Why the last update happened.
208
+ */
209
+ updateReason: UseFilterUpdateReason
210
+
211
+ /**
212
+ * Update one or more values in your filter.
213
+ */
214
+ updateFilter: (
215
+ filter: Partial<TFilter>,
216
+ shouldRunImmediately?: boolean,
217
+ ) => void
218
+
219
+ /**
220
+ * Resets the filter back to `defaultFilterInfo`.
221
+ */
222
+ resetFilter: (shouldRunImmediately?: boolean) => void
223
+
224
+ /**
225
+ * Changes the offset. Will update `page`.
226
+ */
227
+ setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void
228
+
229
+ /**
230
+ * Changes the page. Will update `offset`.
231
+ */
232
+ setPage: (page: number | string, shouldRunImmediately?: boolean) => void
233
+
234
+ /**
235
+ * Changes the page size.
236
+ */
237
+ setPageSize: (
238
+ pageSize: number | string,
239
+ shouldRunImmediately?: boolean,
240
+ ) => void
241
+
242
+ /**
243
+ * Change the sort method.
244
+ */
245
+ setSort: (sort: string | undefined, shouldRunImmediately?: boolean) => void
246
+
247
+ /**
248
+ * Changes the cursor.
249
+ */
250
+ setCursor: (cursor: string | null | undefined) => void
251
+
252
+ /**
253
+ * Forces a refresh of the filter.
254
+ */
255
+ forceRefresh: (shouldRunImmediately?: boolean) => void
256
+ }
257
+
258
+ interface FilterInfo<TFilter> {
259
+ filter: TFilter
260
+ offset: number
261
+ page: number
262
+ sort?: string
263
+ pageSize: number
264
+ lastRefreshAt: number
265
+ totalResults: number
266
+ totalPages: number
267
+ shouldRunImmediately: boolean
268
+ cursor?: string | null
269
+ nextCursor?: string | null
270
+ }
271
+ ```
272
+
273
+ ### `useSimpleFilter(options)`
274
+
275
+ ```tsx
276
+ interface UseSimpleFilterOptions<TFilter> {
277
+ /**
278
+ * Default values for your filter. When calling `.reset` your filter will be
279
+ * set to this.
280
+ * Changing these values does not cause a call to happen.
281
+ */
282
+ defaultFilterInfo?: Partial<SimpleFilterInfo<TFilter>>
283
+
284
+ /**
285
+ * In case you want to change the default debounce duration.
286
+ */
287
+ debounceDuration?: number
288
+ }
289
+
290
+ export interface SimpleFilterApi<TFilter> {
291
+ /**
292
+ * Really more "is debouncing", since simple mode doesn't know when your code
293
+ * is doing anything.
294
+ */
295
+ isLoading: boolean
296
+ /**
297
+ * Access to the filter and its metadata.
298
+ */
299
+ filterInfo: SimpleFilterInfo<TFilter>
300
+ /**
301
+ * Same as the regular `filterInfo`, but updates in a debounced fashion.
302
+ */
303
+ debouncedFilterInfo: SimpleFilterInfo<TFilter>
304
+ /**
305
+ * Why the last update happened.
306
+ */
307
+ updateReason: UseFilterUpdateReason
308
+
309
+ /**
310
+ * Update one or more values in your filter.
311
+ */
312
+ updateFilter: (
313
+ filter: Partial<TFilter>,
314
+ shouldRunImmediately?: boolean,
315
+ ) => void
316
+
317
+ /**
318
+ * Does the boring math for you based on your `pageSize` and `totalResults`.
319
+ */
320
+ pagingInfo: (total: string | number | null | undefined) => {
321
+ totalResults: number
322
+ totalPages: number
323
+ }
324
+
325
+ /**
326
+ * Resets the filter back to `defaultFilterInfo`.
327
+ */
328
+ resetFilter: (shouldRunImmediately?: boolean) => void
329
+
330
+ /**
331
+ * Changes the offset. Will update `page`.
332
+ */
333
+ setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void
334
+
335
+ /**
336
+ * Changes the page. Will update `offset`.
337
+ */
338
+ setPage: (page: number | string, shouldRunImmediately?: boolean) => void
339
+
340
+ /**
341
+ * Changes the page size.
342
+ */
343
+ setPageSize: (
344
+ pageSize: number | string,
345
+ shouldRunImmediately?: boolean,
346
+ ) => void
347
+
348
+ /**
349
+ * Change the sort method.
350
+ */
351
+ setSort: (sort: string, shouldRunImmediately?: boolean) => void
352
+
353
+ /**
354
+ * Changes the cursor.
355
+ */
356
+ setCursor: (
357
+ cursor: string | null | undefined,
358
+ shouldRunImmediately?: boolean,
359
+ ) => void
360
+
361
+ /**
362
+ * Forces a refresh of the filter.
363
+ */
364
+ forceRefresh: (shouldRunImmediately?: boolean) => void
365
+ }
366
+
367
+ interface SimpleFilterInfo<TFilter> {
368
+ filter: TFilter
369
+ sort?: string
370
+ offset: number
371
+ page: number
372
+ pageSize: number
373
+ lastRefreshAt: number
374
+ cursor?: string | null
375
+ shouldRunImmediately: boolean
376
+ }
377
+ ```
378
+
379
+ ## Stores
380
+
381
+ Persistence is provided by "stores". use-filter comes with a few included:
382
+
383
+ - `memoryStore`: For when you want persistence across page views in your app, but not refreshes.
384
+ - `localStorageStore`: For when you want full persistence of filters.
385
+ - `reduxStore`: For when you want full persistence of filters and any extra goodies your redux middleware might bring you.
386
+
387
+ To use a store, simply:
388
+
389
+ ```tsx
390
+ import { setFilterStore } from '@promoboxx/use-filter/dist/store'
391
+ import localStorageStore from '@promoboxx/use-filter/dist/store/localStorageStore'
392
+
393
+ setFilterStore(localStorageStore)
394
+ ```
395
+
396
+ ### Creating a store
397
+
398
+ If you would like to build your own, a store must match this interface:
399
+
400
+ ```tsx
401
+ interface FilterStore {
402
+ getFilter<TFilter = any>(
403
+ namespace: string,
404
+ ): FilterInfo<TFilter> | null | undefined
405
+ saveFilter<TFilter = any>(
406
+ namespace: string,
407
+ filter: FilterInfo<TFilter>,
408
+ ): void
409
+ getData<TResult = any>(namespace: string): TResult | null | undefined
410
+ saveData<TResult = any>(namespace: string, data: TResult): void
411
+ clear(): void
412
+ }
413
+ ```
@@ -1,26 +1,81 @@
1
1
  declare type MaybePromise<T> = T | Promise<T>;
2
2
  interface UseFilterOptions<TFilter, TResult> {
3
+ /**
4
+ * Default values for your filter. When calling `.reset` your filter will be
5
+ * set to this.
6
+ * Changing these values does not cause a call to happen.
7
+ */
3
8
  defaultFilterInfo?: Partial<FilterInfo<TFilter>>;
9
+ /**
10
+ * Enable this if you are not using the data caching of this hook.
11
+ */
4
12
  shouldForceRunOnMount?: boolean;
13
+ /**
14
+ * Called whenever the filter changes.
15
+ */
5
16
  onChange: (filterInfo: FilterInfo<TFilter>, reason: UseFilterUpdateReason) => MaybePromise<UseFilterOnChangeResult<TFilter, TResult>>;
17
+ /**
18
+ * In case you want to change the default debounce duration.
19
+ */
6
20
  debounceDuration?: number;
7
21
  }
8
- export declare type UseFilterUpdateReason = 'initial' | 'filter' | 'pagination';
9
- declare function useFilter<TFilter, TResult>(namespace: string, options: UseFilterOptions<TFilter, TResult>): {
22
+ export interface FilterApi<TFilter, TResult> {
23
+ /**
24
+ * Whether the system is debouncing or waiting for your `onChange` to finish.
25
+ */
10
26
  isLoading: boolean;
27
+ /**
28
+ * Any cached data for this filter.
29
+ */
11
30
  data: TResult | null | undefined;
12
- doesFilterExist: boolean;
31
+ /**
32
+ * Access to the filter and its metadata.
33
+ */
13
34
  filterInfo: FilterInfo<TFilter>;
35
+ /**
36
+ * Whether the filter existed in the system when the component mounted.
37
+ * @deprecated
38
+ */
39
+ doesFilterExist: boolean;
40
+ /**
41
+ * Why the last update happened.
42
+ */
14
43
  updateReason: UseFilterUpdateReason;
44
+ /**
45
+ * Update one or more values in your filter.
46
+ */
15
47
  updateFilter: (filter: Partial<TFilter>, shouldRunImmediately?: boolean) => void;
48
+ /**
49
+ * Resets the filter back to `defaultFilterInfo`.
50
+ */
16
51
  resetFilter: (shouldRunImmediately?: boolean) => void;
52
+ /**
53
+ * Changes the offset. Will update `page`.
54
+ */
17
55
  setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void;
56
+ /**
57
+ * Changes the page. Will update `offset`.
58
+ */
18
59
  setPage: (page: number | string, shouldRunImmediately?: boolean) => void;
60
+ /**
61
+ * Changes the page size.
62
+ */
19
63
  setPageSize: (pageSize: number | string, shouldRunImmediately?: boolean) => void;
64
+ /**
65
+ * Change the sort method.
66
+ */
20
67
  setSort: (sort: string | undefined, shouldRunImmediately?: boolean) => void;
21
- setCursor: (cursor: string | null | undefined, shouldRunImmediately?: boolean) => void;
68
+ /**
69
+ * Changes the cursor.
70
+ */
71
+ setCursor: (cursor: string | null | undefined) => void;
72
+ /**
73
+ * Forces a refresh of the filter.
74
+ */
22
75
  forceRefresh: (shouldRunImmediately?: boolean) => void;
23
- };
76
+ }
77
+ export declare type UseFilterUpdateReason = 'initial' | 'filter' | 'pagination';
78
+ declare function useFilter<TFilter, TResult>(namespace: string, options: UseFilterOptions<TFilter, TResult>): FilterApi<TFilter, TResult>;
24
79
  export default useFilter;
25
80
  export interface FilterInfo<TFilter> {
26
81
  filter: TFilter;
package/dist/useFilter.js CHANGED
@@ -149,7 +149,7 @@ function useFilter(namespace, options) {
149
149
  }
150
150
  };
151
151
  }, [filterInfo, namespace]);
152
- return {
152
+ var api = {
153
153
  isLoading: isLoading,
154
154
  data: data,
155
155
  doesFilterExist: doesFilterExist.current,
@@ -226,6 +226,7 @@ function useFilter(namespace, options) {
226
226
  });
227
227
  }, []),
228
228
  };
229
+ return api;
229
230
  }
230
231
  exports.default = useFilter;
231
232
  function invariant(input, message) {
@@ -1,52 +1,83 @@
1
1
  import { UseFilterUpdateReason } from './useFilter';
2
- export interface SimpleFilterInfo<TFilter> {
3
- filter: TFilter;
4
- sort?: string;
5
- offset: number;
6
- page: number;
7
- pageSize: number;
8
- lastRefreshAt: number;
9
- cursor?: string | null;
10
- shouldRunImmediately: boolean;
11
- }
12
2
  interface UseSimpleFilterOptions<TFilter> {
3
+ /**
4
+ * Default values for your filter. When calling `.reset` your filter will be
5
+ * set to this.
6
+ * Changing these values does not cause a call to happen.
7
+ */
13
8
  defaultFilterInfo?: Partial<SimpleFilterInfo<TFilter>>;
9
+ /**
10
+ * In case you want to change the default debounce duration.
11
+ */
14
12
  debounceDuration?: number;
15
13
  }
16
- declare function useSimpleFilter<TFilter>(namespace: string, options: UseSimpleFilterOptions<TFilter>): {
14
+ export interface SimpleFilterApi<TFilter> {
15
+ /**
16
+ * Really more "is debouncing", since simple mode doesn't know when your code
17
+ * is doing anything.
18
+ */
17
19
  isLoading: boolean;
18
- filterInfo: {
19
- shouldRunImmediately: boolean;
20
- filter: TFilter;
21
- sort?: string | undefined;
22
- offset: number;
23
- page: number;
24
- pageSize: number;
25
- lastRefreshAt: number;
26
- cursor?: string | null | undefined;
27
- };
28
- debouncedFilterInfo: {
29
- shouldRunImmediately: boolean;
30
- filter: TFilter;
31
- sort?: string | undefined;
32
- offset: number;
33
- page: number;
34
- pageSize: number;
35
- lastRefreshAt: number;
36
- cursor?: string | null | undefined;
37
- };
20
+ /**
21
+ * Access to the filter and its metadata.
22
+ */
23
+ filterInfo: SimpleFilterInfo<TFilter>;
24
+ /**
25
+ * Same as the regular `filterInfo`, but updates in a debounced fashion.
26
+ */
27
+ debouncedFilterInfo: SimpleFilterInfo<TFilter>;
28
+ /**
29
+ * Why the last update happened.
30
+ */
38
31
  updateReason: UseFilterUpdateReason;
39
- updateFilter: (filter: Partial<TFilter>, shouldRunImmediately?: any) => void;
32
+ /**
33
+ * Update one or more values in your filter.
34
+ */
35
+ updateFilter: (filter: Partial<TFilter>, shouldRunImmediately?: boolean) => void;
36
+ /**
37
+ * Does the boring math for you based on your `pageSize` and `totalResults`.
38
+ */
40
39
  pagingInfo: (total: string | number | null | undefined) => {
41
40
  totalResults: number;
42
41
  totalPages: number;
43
42
  };
43
+ /**
44
+ * Resets the filter back to `defaultFilterInfo`.
45
+ */
44
46
  resetFilter: (shouldRunImmediately?: boolean) => void;
47
+ /**
48
+ * Changes the offset. Will update `page`.
49
+ */
45
50
  setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void;
51
+ /**
52
+ * Changes the page. Will update `offset`.
53
+ */
46
54
  setPage: (page: number | string, shouldRunImmediately?: boolean) => void;
55
+ /**
56
+ * Changes the page size.
57
+ */
47
58
  setPageSize: (pageSize: number | string, shouldRunImmediately?: boolean) => void;
59
+ /**
60
+ * Change the sort method.
61
+ */
48
62
  setSort: (sort: string, shouldRunImmediately?: boolean) => void;
63
+ /**
64
+ * Changes the cursor.
65
+ */
49
66
  setCursor: (cursor: string | null | undefined, shouldRunImmediately?: boolean) => void;
67
+ /**
68
+ * Forces a refresh of the filter.
69
+ */
50
70
  forceRefresh: (shouldRunImmediately?: boolean) => void;
51
- };
71
+ }
72
+ export interface SimpleFilterInfo<TFilter> {
73
+ filter: TFilter;
74
+ sort?: string;
75
+ offset: number;
76
+ page: number;
77
+ pageSize: number;
78
+ lastRefreshAt: number;
79
+ cursor?: string | null;
80
+ shouldRunImmediately: boolean;
81
+ }
82
+ declare function useSimpleFilter<TFilter>(namespace: string, options: UseSimpleFilterOptions<TFilter>): SimpleFilterApi<TFilter>;
52
83
  export default useSimpleFilter;
@@ -66,7 +66,7 @@ function useSimpleFilter(namespace, options) {
66
66
  };
67
67
  }, [filterInfo]);
68
68
  var previousTotalRef = react_1.useRef(0);
69
- return {
69
+ var api = {
70
70
  isLoading: isLoading,
71
71
  filterInfo: filterInfo,
72
72
  debouncedFilterInfo: debouncedFilterInfo,
@@ -136,6 +136,7 @@ function useSimpleFilter(namespace, options) {
136
136
  setFilterInfoState(function (previous) { return (__assign(__assign({}, previous), { lastRefreshAt: new Date().getTime(), shouldRunImmediately: shouldRunImmediately })); });
137
137
  }, []),
138
138
  };
139
+ return api;
139
140
  }
140
141
  function buildDefaultFilterInfo(filterInfo) {
141
142
  if (filterInfo === void 0) { filterInfo = {}; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promoboxx/use-filter",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "",
5
5
  "main": "dist/useFilter.js",
6
6
  "keywords": [],