@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
package/src/useFilter.ts DELETED
@@ -1,524 +0,0 @@
1
- import { useEffect, useRef, useCallback, useState } from 'react'
2
-
3
- import buildDefaultFilterInfo from './lib/buildDefaultFilterInfo'
4
- import getOffsetFromPage from './lib/getOffsetFromPage'
5
- import getPageFromOffset from './lib/getPageFromOffset'
6
- import shallowEqual from './lib/shallowEqual'
7
- import { getFilterStore } from './store'
8
- import type { FilterStore } from './store'
9
-
10
- type MaybePromise<T> = T | Promise<T>
11
-
12
- interface UseFilterOptions<TFilter extends Record<string, unknown>, TResult> {
13
- /**
14
- * Default values for your filter. When calling `.reset` your filter will be
15
- * set to this.
16
- * Changing these values does not cause a call to happen.
17
- */
18
- defaultFilterInfo?: Partial<FilterInfo<TFilter>>
19
- /**
20
- * Enable this if you are not using the data caching of this hook.
21
- */
22
- shouldForceRunOnMount?: boolean
23
- /**
24
- * Called whenever the filter changes.
25
- */
26
- onChange: (
27
- filterInfo: FilterInfo<TFilter>,
28
- reason: UseFilterUpdateReason,
29
- ) => MaybePromise<UseFilterOnChangeResult<TFilter, TResult>>
30
- /**
31
- * In case you want to change the default debounce duration.
32
- */
33
- debounceDuration?: number
34
- onBeforeSaveFilter?: (filterInfo: FilterInfo<TFilter>) => FilterInfo<TFilter>
35
-
36
- store?: FilterStore
37
- }
38
-
39
- export interface FilterApi<TFilter extends Record<string, unknown>, TResult> {
40
- /**
41
- * Whether the system is debouncing or waiting for your `onChange` to finish.
42
- */
43
- isLoading: boolean
44
- /**
45
- * Any cached data for this filter.
46
- */
47
- data: TResult | null | undefined
48
- /**
49
- * Access to the filter and its metadata.
50
- */
51
- filterInfo: FilterInfo<TFilter>
52
-
53
- /**
54
- * Whether the filter existed in the system when the component mounted.
55
- * @deprecated
56
- */
57
- doesFilterExist: boolean
58
- /**
59
- * Why the last update happened.
60
- */
61
- updateReason: UseFilterUpdateReason
62
-
63
- /**
64
- * Update one or more values in your filter.
65
- */
66
- updateFilter: (
67
- filter: Partial<TFilter>,
68
- shouldRunImmediately?: boolean,
69
- ) => void
70
-
71
- /**
72
- * Resets the filter back to `defaultFilterInfo`.
73
- */
74
- resetFilter: (shouldRunImmediately?: boolean) => void
75
-
76
- /**
77
- * Changes the offset. Will update `page`.
78
- */
79
- setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void
80
-
81
- /**
82
- * Changes the page. Will update `offset`.
83
- */
84
- setPage: (page: number | string, shouldRunImmediately?: boolean) => void
85
-
86
- /**
87
- * Changes the page size.
88
- */
89
- setPageSize: (
90
- pageSize: number | string,
91
- shouldRunImmediately?: boolean,
92
- ) => void
93
-
94
- /**
95
- * Change the sort method.
96
- */
97
- setSort: (sort: string | undefined, shouldRunImmediately?: boolean) => void
98
-
99
- /**
100
- * Changes the cursor.
101
- */
102
- setCursor: (
103
- cursor: string | null | undefined,
104
- shouldRunImmediately?: boolean,
105
- ) => void
106
-
107
- /**
108
- * Forces a refresh of the filter.
109
- */
110
- forceRefresh: (shouldRunImmediately?: boolean) => void
111
- }
112
-
113
- export type UseFilterUpdateReason = 'initial' | 'filter' | 'pagination'
114
-
115
- function useFilter<TFilter extends Record<string, unknown>, TResult>(
116
- namespace: string,
117
- options: UseFilterOptions<TFilter, TResult>,
118
- ) {
119
- // Having these types of refs helps get around rules of hooks, and they need
120
- // to be updated each render.
121
- const ctxRefValue = {
122
- namespace,
123
- onChange: options.onChange,
124
- debounceDuration: options.debounceDuration,
125
- defaultFilterInfo: options.defaultFilterInfo,
126
- onBeforeSaveFilter: options.onBeforeSaveFilter,
127
- store: options.store,
128
- }
129
- const ctxRef = useRef(ctxRefValue)
130
- useEffect(() => {
131
- ctxRef.current = ctxRefValue
132
- })
133
-
134
- const [filterInfo, setFilterInfoState] = useState(() => {
135
- const fromStore = getFilterStore(ctxRef.current.store).getFilter<TFilter>(
136
- namespace,
137
- )
138
-
139
- // If the filter is already in the store, we have to make sure it ...
140
- // - has both the filter and filterInfo
141
- // - has all the fields the user passed to the hook
142
- if (fromStore) {
143
- const filterInfo = buildDefaultFilterInfo({
144
- ...fromStore,
145
- shouldRunImmediately: true,
146
- filter: {
147
- ...options.defaultFilterInfo?.filter,
148
- ...fromStore.filter,
149
- },
150
- })
151
-
152
- return filterInfo
153
- }
154
- })
155
- const [data, setData] = useState(() =>
156
- getFilterStore(ctxRef.current.store).getData<TResult>(namespace),
157
- )
158
-
159
- const [isLoading, setIsLoading] = useState(!filterInfo)
160
- const doesFilterExist = useRef(!!filterInfo)
161
- const lastRefreshAtRef = useRef(
162
- // if shouldForceRunOnMount is set, always use -1
163
- // otherwise, try grabbing lastRefreshAt
164
- // then, just use -1 cause there's no other option
165
- options.shouldForceRunOnMount
166
- ? -1
167
- : filterInfo
168
- ? filterInfo.lastRefreshAt
169
- : -1,
170
- )
171
-
172
- const updateReasonRef = useRef<UseFilterUpdateReason>('initial')
173
-
174
- // Call onChange when data changes.
175
- useEffect(() => {
176
- setIsLoading(true)
177
-
178
- // If there is no existing filter info, set it to the defaults and return.
179
- // This same effect will trigger next go.
180
- if (!filterInfo) {
181
- setFilterInfoState({
182
- ...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
183
- shouldRunImmediately: true,
184
- })
185
- return
186
- }
187
-
188
- // If the lastRefreshAt hasn't changed, don't make a request.
189
- if (lastRefreshAtRef.current === filterInfo.lastRefreshAt) {
190
- setIsLoading(false)
191
- return
192
- }
193
-
194
- const makeRequest = () => {
195
- lastRefreshAtRef.current = filterInfo.lastRefreshAt
196
- const response = ctxRef.current.onChange(
197
- filterInfo,
198
- updateReasonRef.current,
199
- )
200
- getFilterStore(ctxRef.current.store).saveFilter(
201
- namespace,
202
- ctxRef.current.onBeforeSaveFilter
203
- ? ctxRef.current.onBeforeSaveFilter(filterInfo)
204
- : filterInfo,
205
- )
206
-
207
- if (!response) {
208
- setIsLoading(false)
209
- return
210
- }
211
-
212
- function handleResponse(
213
- response?: UseFilterOnChangeResult<TFilter, TResult>,
214
- ) {
215
- if (!response) {
216
- return
217
- }
218
-
219
- if (response.filterInfo) {
220
- setFilterInfoState((previous) => {
221
- // Even though we've already checked for response.filterInfo, since
222
- // we're in a callback TypeScript still considers accessing it
223
- // unsafe, so do another check.
224
- invariant(!!response.filterInfo, '')
225
-
226
- invariant(
227
- previous != null,
228
- 'handleResponse called without filterInfo',
229
- )
230
-
231
- const extra: Partial<FilterInfo<TFilter>> = {}
232
- const {
233
- page,
234
- offset,
235
- totalResults,
236
- pageSize,
237
- nextCursor,
238
- ...filterInfoFromResponse
239
- } = response.filterInfo
240
-
241
- const pageSizeNumber = Number(pageSize || previous.pageSize)
242
-
243
- if (page != null && offset == null) {
244
- extra.offset = getOffsetFromPage(page, pageSizeNumber)
245
- extra.page = page
246
- // If there's an offset but no page, set the page.
247
- } else if (page == null && offset != null) {
248
- const offsetNumber = Number(offset)
249
- extra.offset = offsetNumber
250
- extra.page = getPageFromOffset(offsetNumber, pageSizeNumber)
251
- }
252
-
253
- // `null` / `undefined` are valid here, it means there is zero
254
- // results, so we can't just check if it's truthy, we need to check
255
- // for presence.
256
- if ('totalResults' in response.filterInfo) {
257
- extra.totalResults = Number(totalResults || 0)
258
- extra.totalPages = Math.ceil(extra.totalResults / pageSizeNumber)
259
- }
260
-
261
- // `null` / `undefined` are valid here, it means there is no next
262
- // cursor, so we can't just check if it's truthy, we need to check
263
- // for presence.
264
- if ('nextCursor' in response.filterInfo) {
265
- extra.nextCursor = nextCursor
266
- }
267
-
268
- return {
269
- ...previous,
270
- ...filterInfoFromResponse,
271
- ...extra,
272
- }
273
- })
274
- }
275
-
276
- if (response.data) {
277
- setData(response.data)
278
- getFilterStore(ctxRef.current.store).saveData(
279
- namespace,
280
- response.data,
281
- )
282
- }
283
- }
284
-
285
- if (isPromise(response)) {
286
- response.then(handleResponse).finally(() => setIsLoading(false))
287
- } else {
288
- handleResponse(response)
289
- setIsLoading(false)
290
- }
291
- }
292
-
293
- let timeout: ReturnType<typeof setTimeout> | undefined = undefined
294
-
295
- if (filterInfo.shouldRunImmediately) {
296
- makeRequest()
297
- } else {
298
- timeout = setTimeout(
299
- makeRequest,
300
- ctxRef.current.debounceDuration != null
301
- ? ctxRef.current.debounceDuration
302
- : 500,
303
- )
304
- }
305
-
306
- return () => {
307
- if (timeout) {
308
- clearTimeout(timeout)
309
- }
310
- }
311
- }, [filterInfo, namespace])
312
-
313
- const api: FilterApi<TFilter, TResult> = {
314
- isLoading,
315
- data,
316
- doesFilterExist: doesFilterExist.current,
317
- filterInfo: filterInfo || buildDefaultFilterInfo(options.defaultFilterInfo),
318
- updateReason: updateReasonRef.current,
319
-
320
- updateFilter: useCallback(
321
- (filter: Partial<TFilter>, shouldRunImmediately: boolean = false) => {
322
- updateReasonRef.current = 'filter'
323
-
324
- setFilterInfoState((previous) => {
325
- invariant(previous != null, 'updateFilter called without filterInfo')
326
-
327
- const nextFilter = {
328
- ...previous.filter,
329
- ...filter,
330
- }
331
-
332
- if (shallowEqual(previous.filter, nextFilter)) {
333
- return previous
334
- }
335
-
336
- return {
337
- ...previous,
338
- offset: 0,
339
- page: 1,
340
- totalResults: 1,
341
- totalPages: 1,
342
- filter: nextFilter,
343
- lastRefreshAt: new Date().getTime(),
344
- cursor: undefined,
345
- nextCursor: undefined,
346
- shouldRunImmediately,
347
- }
348
- })
349
- },
350
- [],
351
- ),
352
-
353
- resetFilter: useCallback((shouldRunImmediately: boolean = false) => {
354
- updateReasonRef.current = 'filter'
355
-
356
- setFilterInfoState({
357
- ...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
358
- lastRefreshAt: new Date().getTime(),
359
- shouldRunImmediately,
360
- })
361
- }, []),
362
-
363
- setOffset: useCallback(
364
- (offset: number | string, shouldRunImmediately: boolean = false) => {
365
- updateReasonRef.current = 'pagination'
366
-
367
- setFilterInfoState((previous) => {
368
- invariant(previous != null, 'setOffset called without filterInfo')
369
-
370
- const offsetNumber = Number(offset)
371
-
372
- return {
373
- ...previous,
374
- offset: offsetNumber,
375
- page: getPageFromOffset(offsetNumber, previous.pageSize),
376
- lastRefreshAt: new Date().getTime(),
377
- shouldRunImmediately,
378
- }
379
- })
380
- },
381
- [],
382
- ),
383
-
384
- setPage: useCallback(
385
- (page: number | string, shouldRunImmediately: boolean = false) => {
386
- updateReasonRef.current = 'pagination'
387
-
388
- setFilterInfoState((previous) => {
389
- invariant(previous != null, 'setPage called without filterInfo')
390
-
391
- const pageNumber = Number(page)
392
-
393
- return {
394
- ...previous,
395
- offset: getOffsetFromPage(pageNumber, previous.pageSize),
396
- page: pageNumber,
397
- lastRefreshAt: new Date().getTime(),
398
- shouldRunImmediately,
399
- }
400
- })
401
- },
402
- [],
403
- ),
404
-
405
- setPageSize: useCallback(
406
- (pageSize: number | string, shouldRunImmediately: boolean = false) => {
407
- updateReasonRef.current = 'pagination'
408
-
409
- setFilterInfoState((previous) => {
410
- invariant(previous != null, 'setPageSize called without filterInfo')
411
-
412
- const pageSizeNumber = Number(pageSize)
413
- const page = getPageFromOffset(previous.offset, pageSizeNumber)
414
- const offset = getOffsetFromPage(page, pageSizeNumber)
415
-
416
- return {
417
- ...previous,
418
- pageSize: pageSizeNumber,
419
- offset,
420
- page,
421
- lastRefreshAt: new Date().getTime(),
422
- shouldRunImmediately,
423
- }
424
- })
425
- },
426
- [],
427
- ),
428
-
429
- setSort: useCallback(
430
- (sort: string | undefined, shouldRunImmediately: boolean = false) => {
431
- updateReasonRef.current = 'filter'
432
-
433
- setFilterInfoState((previous) => {
434
- invariant(previous != null, 'setSort called without filterInfo')
435
-
436
- return {
437
- ...previous,
438
- sort,
439
- lastRefreshAt: new Date().getTime(),
440
- shouldRunImmediately,
441
- }
442
- })
443
- },
444
- [],
445
- ),
446
-
447
- setCursor: useCallback(
448
- (
449
- cursor: string | null | undefined,
450
- shouldRunImmediately: boolean = false,
451
- ) => {
452
- setFilterInfoState((previous) => {
453
- updateReasonRef.current = 'pagination'
454
-
455
- invariant(previous != null, 'setCursor called without filterInfo')
456
-
457
- return {
458
- ...previous,
459
- cursor,
460
- lastRefreshAt: new Date().getTime(),
461
- shouldRunImmediately,
462
- }
463
- })
464
- },
465
- [],
466
- ),
467
-
468
- forceRefresh: useCallback((shouldRunImmediately: boolean = false) => {
469
- updateReasonRef.current = 'filter'
470
-
471
- setFilterInfoState((previous) => {
472
- invariant(previous != null, 'forceRefresh called without filterInfo')
473
-
474
- return {
475
- ...previous,
476
- lastRefreshAt: new Date().getTime(),
477
- shouldRunImmediately,
478
- }
479
- })
480
- }, []),
481
- }
482
-
483
- return api
484
- }
485
-
486
- export default useFilter
487
-
488
- export interface FilterInfo<TFilter extends Record<string, unknown>> {
489
- filter: TFilter
490
- offset: number
491
- page: number
492
- sort?: string
493
- pageSize: number
494
- lastRefreshAt: number
495
- totalResults: number
496
- totalPages: number
497
- shouldRunImmediately: boolean
498
- cursor?: string | null
499
- nextCursor?: string | null
500
- }
501
-
502
- type UseFilterOnChangeResult<
503
- TFilter extends Record<string, unknown>,
504
- TResult,
505
- > = void | {
506
- filterInfo?: Partial<
507
- Omit<FilterInfo<TFilter>, 'totalResults' | 'offset' | 'pageSize'> & {
508
- totalResults: string | number
509
- offset: string | number
510
- pageSize: string | number
511
- }
512
- >
513
- data?: TResult
514
- }
515
-
516
- function invariant(input: boolean, message: string): asserts input {
517
- if (!input) {
518
- throw new Error(`Invariant error: ${message}`)
519
- }
520
- }
521
-
522
- function isPromise(t?: any): t is Promise<any> {
523
- return !!t?.then
524
- }