@tanstack/preact-pacer 0.19.3 → 0.20.0

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 (196) hide show
  1. package/dist/async-batcher/index.cjs +1 -0
  2. package/dist/async-batcher/index.d.cts +2 -2
  3. package/dist/async-batcher/index.d.ts +2 -2
  4. package/dist/async-batcher/useAsyncBatchedCallback.cjs +2 -2
  5. package/dist/async-batcher/useAsyncBatchedCallback.cjs.map +1 -1
  6. package/dist/async-batcher/useAsyncBatchedCallback.d.cts +2 -4
  7. package/dist/async-batcher/useAsyncBatchedCallback.d.ts +2 -4
  8. package/dist/async-batcher/useAsyncBatchedCallback.js +2 -2
  9. package/dist/async-batcher/useAsyncBatchedCallback.js.map +1 -1
  10. package/dist/async-batcher/useAsyncBatcher.cjs +28 -0
  11. package/dist/async-batcher/useAsyncBatcher.cjs.map +1 -1
  12. package/dist/async-batcher/useAsyncBatcher.d.cts +28 -2
  13. package/dist/async-batcher/useAsyncBatcher.d.ts +28 -2
  14. package/dist/async-batcher/useAsyncBatcher.js +29 -1
  15. package/dist/async-batcher/useAsyncBatcher.js.map +1 -1
  16. package/dist/async-debouncer/index.cjs +1 -0
  17. package/dist/async-debouncer/index.d.cts +2 -2
  18. package/dist/async-debouncer/index.d.ts +2 -2
  19. package/dist/async-debouncer/useAsyncDebouncedCallback.cjs.map +1 -1
  20. package/dist/async-debouncer/useAsyncDebouncedCallback.d.cts +2 -3
  21. package/dist/async-debouncer/useAsyncDebouncedCallback.d.ts +2 -3
  22. package/dist/async-debouncer/useAsyncDebouncedCallback.js.map +1 -1
  23. package/dist/async-debouncer/useAsyncDebouncer.cjs +24 -2
  24. package/dist/async-debouncer/useAsyncDebouncer.cjs.map +1 -1
  25. package/dist/async-debouncer/useAsyncDebouncer.d.cts +28 -3
  26. package/dist/async-debouncer/useAsyncDebouncer.d.ts +27 -2
  27. package/dist/async-debouncer/useAsyncDebouncer.js +24 -2
  28. package/dist/async-debouncer/useAsyncDebouncer.js.map +1 -1
  29. package/dist/async-queuer/index.cjs +1 -0
  30. package/dist/async-queuer/index.d.cts +2 -2
  31. package/dist/async-queuer/index.d.ts +2 -2
  32. package/dist/async-queuer/useAsyncQueuedState.cjs.map +1 -1
  33. package/dist/async-queuer/useAsyncQueuedState.d.cts +3 -4
  34. package/dist/async-queuer/useAsyncQueuedState.d.ts +3 -4
  35. package/dist/async-queuer/useAsyncQueuedState.js.map +1 -1
  36. package/dist/async-queuer/useAsyncQueuer.cjs +28 -0
  37. package/dist/async-queuer/useAsyncQueuer.cjs.map +1 -1
  38. package/dist/async-queuer/useAsyncQueuer.d.cts +28 -2
  39. package/dist/async-queuer/useAsyncQueuer.d.ts +28 -2
  40. package/dist/async-queuer/useAsyncQueuer.js +29 -1
  41. package/dist/async-queuer/useAsyncQueuer.js.map +1 -1
  42. package/dist/async-rate-limiter/index.cjs +1 -0
  43. package/dist/async-rate-limiter/index.d.cts +2 -2
  44. package/dist/async-rate-limiter/index.d.ts +2 -2
  45. package/dist/async-rate-limiter/useAsyncRateLimitedCallback.cjs.map +1 -1
  46. package/dist/async-rate-limiter/useAsyncRateLimitedCallback.d.cts +2 -3
  47. package/dist/async-rate-limiter/useAsyncRateLimitedCallback.d.ts +2 -3
  48. package/dist/async-rate-limiter/useAsyncRateLimitedCallback.js.map +1 -1
  49. package/dist/async-rate-limiter/useAsyncRateLimiter.cjs +12 -0
  50. package/dist/async-rate-limiter/useAsyncRateLimiter.cjs.map +1 -1
  51. package/dist/async-rate-limiter/useAsyncRateLimiter.d.cts +15 -2
  52. package/dist/async-rate-limiter/useAsyncRateLimiter.d.ts +15 -2
  53. package/dist/async-rate-limiter/useAsyncRateLimiter.js +13 -1
  54. package/dist/async-rate-limiter/useAsyncRateLimiter.js.map +1 -1
  55. package/dist/async-throttler/index.cjs +1 -0
  56. package/dist/async-throttler/index.d.cts +2 -2
  57. package/dist/async-throttler/index.d.ts +2 -2
  58. package/dist/async-throttler/useAsyncThrottledCallback.cjs.map +1 -1
  59. package/dist/async-throttler/useAsyncThrottledCallback.d.cts +2 -3
  60. package/dist/async-throttler/useAsyncThrottledCallback.d.ts +2 -3
  61. package/dist/async-throttler/useAsyncThrottledCallback.js.map +1 -1
  62. package/dist/async-throttler/useAsyncThrottler.cjs +26 -2
  63. package/dist/async-throttler/useAsyncThrottler.cjs.map +1 -1
  64. package/dist/async-throttler/useAsyncThrottler.d.cts +27 -2
  65. package/dist/async-throttler/useAsyncThrottler.d.ts +27 -2
  66. package/dist/async-throttler/useAsyncThrottler.js +26 -2
  67. package/dist/async-throttler/useAsyncThrottler.js.map +1 -1
  68. package/dist/batcher/index.cjs +1 -0
  69. package/dist/batcher/index.d.cts +2 -2
  70. package/dist/batcher/index.d.ts +2 -2
  71. package/dist/batcher/useBatchedCallback.cjs +1 -1
  72. package/dist/batcher/useBatchedCallback.cjs.map +1 -1
  73. package/dist/batcher/useBatchedCallback.d.cts +2 -4
  74. package/dist/batcher/useBatchedCallback.d.ts +2 -4
  75. package/dist/batcher/useBatchedCallback.js +1 -1
  76. package/dist/batcher/useBatchedCallback.js.map +1 -1
  77. package/dist/batcher/useBatcher.cjs +19 -0
  78. package/dist/batcher/useBatcher.cjs.map +1 -1
  79. package/dist/batcher/useBatcher.d.cts +22 -2
  80. package/dist/batcher/useBatcher.d.ts +22 -2
  81. package/dist/batcher/useBatcher.js +20 -1
  82. package/dist/batcher/useBatcher.js.map +1 -1
  83. package/dist/debouncer/index.cjs +1 -0
  84. package/dist/debouncer/index.d.cts +2 -2
  85. package/dist/debouncer/index.d.ts +2 -2
  86. package/dist/debouncer/useDebouncedCallback.cjs.map +1 -1
  87. package/dist/debouncer/useDebouncedCallback.d.cts +2 -3
  88. package/dist/debouncer/useDebouncedCallback.d.ts +2 -3
  89. package/dist/debouncer/useDebouncedCallback.js.map +1 -1
  90. package/dist/debouncer/useDebouncedState.cjs.map +1 -1
  91. package/dist/debouncer/useDebouncedState.d.cts +3 -4
  92. package/dist/debouncer/useDebouncedState.d.ts +3 -4
  93. package/dist/debouncer/useDebouncedState.js.map +1 -1
  94. package/dist/debouncer/useDebouncedValue.cjs.map +1 -1
  95. package/dist/debouncer/useDebouncedValue.d.cts +3 -4
  96. package/dist/debouncer/useDebouncedValue.d.ts +3 -4
  97. package/dist/debouncer/useDebouncedValue.js.map +1 -1
  98. package/dist/debouncer/useDebouncer.cjs +15 -2
  99. package/dist/debouncer/useDebouncer.cjs.map +1 -1
  100. package/dist/debouncer/useDebouncer.d.cts +21 -2
  101. package/dist/debouncer/useDebouncer.d.ts +21 -2
  102. package/dist/debouncer/useDebouncer.js +15 -2
  103. package/dist/debouncer/useDebouncer.js.map +1 -1
  104. package/dist/index.cjs +1 -0
  105. package/dist/index.d.cts +11 -11
  106. package/dist/index.d.ts +11 -11
  107. package/dist/provider/PacerProvider.d.cts +2 -2
  108. package/dist/provider/PacerProvider.d.ts +2 -2
  109. package/dist/provider/index.cjs +1 -0
  110. package/dist/queuer/index.cjs +1 -0
  111. package/dist/queuer/index.d.cts +2 -2
  112. package/dist/queuer/index.d.ts +2 -2
  113. package/dist/queuer/useQueuedState.cjs.map +1 -1
  114. package/dist/queuer/useQueuedState.d.cts +3 -4
  115. package/dist/queuer/useQueuedState.d.ts +3 -4
  116. package/dist/queuer/useQueuedState.js.map +1 -1
  117. package/dist/queuer/useQueuedValue.cjs.map +1 -1
  118. package/dist/queuer/useQueuedValue.d.cts +3 -4
  119. package/dist/queuer/useQueuedValue.d.ts +3 -4
  120. package/dist/queuer/useQueuedValue.js.map +1 -1
  121. package/dist/queuer/useQueuer.cjs +19 -0
  122. package/dist/queuer/useQueuer.cjs.map +1 -1
  123. package/dist/queuer/useQueuer.d.cts +22 -2
  124. package/dist/queuer/useQueuer.d.ts +22 -2
  125. package/dist/queuer/useQueuer.js +20 -1
  126. package/dist/queuer/useQueuer.js.map +1 -1
  127. package/dist/rate-limiter/index.cjs +1 -0
  128. package/dist/rate-limiter/index.d.cts +2 -2
  129. package/dist/rate-limiter/index.d.ts +2 -2
  130. package/dist/rate-limiter/useRateLimitedCallback.cjs.map +1 -1
  131. package/dist/rate-limiter/useRateLimitedCallback.d.cts +2 -3
  132. package/dist/rate-limiter/useRateLimitedCallback.d.ts +2 -3
  133. package/dist/rate-limiter/useRateLimitedCallback.js.map +1 -1
  134. package/dist/rate-limiter/useRateLimitedState.cjs.map +1 -1
  135. package/dist/rate-limiter/useRateLimitedState.d.cts +3 -4
  136. package/dist/rate-limiter/useRateLimitedState.d.ts +3 -4
  137. package/dist/rate-limiter/useRateLimitedState.js.map +1 -1
  138. package/dist/rate-limiter/useRateLimitedValue.cjs.map +1 -1
  139. package/dist/rate-limiter/useRateLimitedValue.d.cts +3 -4
  140. package/dist/rate-limiter/useRateLimitedValue.d.ts +3 -4
  141. package/dist/rate-limiter/useRateLimitedValue.js.map +1 -1
  142. package/dist/rate-limiter/useRateLimiter.cjs +5 -0
  143. package/dist/rate-limiter/useRateLimiter.cjs.map +1 -1
  144. package/dist/rate-limiter/useRateLimiter.d.cts +9 -2
  145. package/dist/rate-limiter/useRateLimiter.d.ts +9 -2
  146. package/dist/rate-limiter/useRateLimiter.js +6 -1
  147. package/dist/rate-limiter/useRateLimiter.js.map +1 -1
  148. package/dist/throttler/index.cjs +1 -0
  149. package/dist/throttler/index.d.cts +2 -2
  150. package/dist/throttler/index.d.ts +2 -2
  151. package/dist/throttler/useThrottledCallback.cjs.map +1 -1
  152. package/dist/throttler/useThrottledCallback.d.cts +2 -3
  153. package/dist/throttler/useThrottledCallback.d.ts +2 -3
  154. package/dist/throttler/useThrottledCallback.js.map +1 -1
  155. package/dist/throttler/useThrottledState.cjs.map +1 -1
  156. package/dist/throttler/useThrottledState.d.cts +3 -4
  157. package/dist/throttler/useThrottledState.d.ts +3 -4
  158. package/dist/throttler/useThrottledState.js.map +1 -1
  159. package/dist/throttler/useThrottledValue.cjs.map +1 -1
  160. package/dist/throttler/useThrottledValue.d.cts +3 -4
  161. package/dist/throttler/useThrottledValue.d.ts +3 -4
  162. package/dist/throttler/useThrottledValue.js.map +1 -1
  163. package/dist/throttler/useThrottler.cjs +15 -2
  164. package/dist/throttler/useThrottler.cjs.map +1 -1
  165. package/dist/throttler/useThrottler.d.cts +21 -2
  166. package/dist/throttler/useThrottler.d.ts +21 -2
  167. package/dist/throttler/useThrottler.js +15 -2
  168. package/dist/throttler/useThrottler.js.map +1 -1
  169. package/package.json +36 -35
  170. package/src/async-batcher/useAsyncBatchedCallback.ts +7 -8
  171. package/src/async-batcher/useAsyncBatcher.ts +46 -4
  172. package/src/async-debouncer/useAsyncDebouncedCallback.ts +2 -2
  173. package/src/async-debouncer/useAsyncDebouncer.ts +40 -5
  174. package/src/async-queuer/useAsyncQueuedState.ts +5 -5
  175. package/src/async-queuer/useAsyncQueuer.ts +46 -4
  176. package/src/async-rate-limiter/useAsyncRateLimitedCallback.ts +2 -2
  177. package/src/async-rate-limiter/useAsyncRateLimiter.ts +32 -3
  178. package/src/async-throttler/useAsyncThrottledCallback.ts +2 -2
  179. package/src/async-throttler/useAsyncThrottler.ts +42 -5
  180. package/src/batcher/useBatchedCallback.ts +6 -10
  181. package/src/batcher/useBatcher.ts +39 -3
  182. package/src/debouncer/useDebouncedCallback.ts +2 -2
  183. package/src/debouncer/useDebouncedState.ts +3 -6
  184. package/src/debouncer/useDebouncedValue.ts +3 -6
  185. package/src/debouncer/useDebouncer.ts +33 -5
  186. package/src/queuer/useQueuedState.ts +3 -3
  187. package/src/queuer/useQueuedValue.ts +3 -3
  188. package/src/queuer/useQueuer.ts +39 -3
  189. package/src/rate-limiter/useRateLimitedCallback.ts +2 -2
  190. package/src/rate-limiter/useRateLimitedState.ts +6 -6
  191. package/src/rate-limiter/useRateLimitedValue.ts +6 -6
  192. package/src/rate-limiter/useRateLimiter.ts +24 -4
  193. package/src/throttler/useThrottledCallback.ts +2 -2
  194. package/src/throttler/useThrottledState.ts +3 -6
  195. package/src/throttler/useThrottledValue.ts +3 -6
  196. package/src/throttler/useThrottler.ts +33 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/preact-pacer",
3
- "version": "0.19.3",
3
+ "version": "0.20.0",
4
4
  "description": "Utilities for debouncing and throttling functions in Preact.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -28,64 +28,64 @@
28
28
  "module": "./dist/index.js",
29
29
  "exports": {
30
30
  ".": {
31
- "require": "./dist/index.cjs",
32
- "import": "./dist/index.js"
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
33
  },
34
34
  "./async-batcher": {
35
- "require": "./dist/async-batcher/index.cjs",
36
- "import": "./dist/async-batcher/index.js"
35
+ "import": "./dist/async-batcher/index.js",
36
+ "require": "./dist/async-batcher/index.cjs"
37
37
  },
38
38
  "./async-debouncer": {
39
- "require": "./dist/async-debouncer/index.cjs",
40
- "import": "./dist/async-debouncer/index.js"
39
+ "import": "./dist/async-debouncer/index.js",
40
+ "require": "./dist/async-debouncer/index.cjs"
41
41
  },
42
42
  "./async-queuer": {
43
- "require": "./dist/async-queuer/index.cjs",
44
- "import": "./dist/async-queuer/index.js"
43
+ "import": "./dist/async-queuer/index.js",
44
+ "require": "./dist/async-queuer/index.cjs"
45
45
  },
46
46
  "./async-rate-limiter": {
47
- "require": "./dist/async-rate-limiter/index.cjs",
48
- "import": "./dist/async-rate-limiter/index.js"
47
+ "import": "./dist/async-rate-limiter/index.js",
48
+ "require": "./dist/async-rate-limiter/index.cjs"
49
49
  },
50
50
  "./async-retryer": {
51
- "require": "./dist/async-retryer/index.cjs",
52
- "import": "./dist/async-retryer/index.js"
51
+ "import": "./dist/async-retryer/index.js",
52
+ "require": "./dist/async-retryer/index.cjs"
53
53
  },
54
54
  "./async-throttler": {
55
- "require": "./dist/async-throttler/index.cjs",
56
- "import": "./dist/async-throttler/index.js"
55
+ "import": "./dist/async-throttler/index.js",
56
+ "require": "./dist/async-throttler/index.cjs"
57
57
  },
58
58
  "./batcher": {
59
- "require": "./dist/batcher/index.cjs",
60
- "import": "./dist/batcher/index.js"
59
+ "import": "./dist/batcher/index.js",
60
+ "require": "./dist/batcher/index.cjs"
61
61
  },
62
62
  "./debouncer": {
63
- "require": "./dist/debouncer/index.cjs",
64
- "import": "./dist/debouncer/index.js"
63
+ "import": "./dist/debouncer/index.js",
64
+ "require": "./dist/debouncer/index.cjs"
65
65
  },
66
66
  "./provider": {
67
- "require": "./dist/provider/index.cjs",
68
- "import": "./dist/provider/index.js"
67
+ "import": "./dist/provider/index.js",
68
+ "require": "./dist/provider/index.cjs"
69
69
  },
70
70
  "./queuer": {
71
- "require": "./dist/queuer/index.cjs",
72
- "import": "./dist/queuer/index.js"
71
+ "import": "./dist/queuer/index.js",
72
+ "require": "./dist/queuer/index.cjs"
73
73
  },
74
74
  "./rate-limiter": {
75
- "require": "./dist/rate-limiter/index.cjs",
76
- "import": "./dist/rate-limiter/index.js"
75
+ "import": "./dist/rate-limiter/index.js",
76
+ "require": "./dist/rate-limiter/index.cjs"
77
77
  },
78
78
  "./throttler": {
79
- "require": "./dist/throttler/index.cjs",
80
- "import": "./dist/throttler/index.js"
79
+ "import": "./dist/throttler/index.js",
80
+ "require": "./dist/throttler/index.cjs"
81
81
  },
82
82
  "./types": {
83
- "require": "./dist/types/index.cjs",
84
- "import": "./dist/types/index.js"
83
+ "import": "./dist/types/index.js",
84
+ "require": "./dist/types/index.cjs"
85
85
  },
86
86
  "./utils": {
87
- "require": "./dist/utils/index.cjs",
88
- "import": "./dist/utils/index.js"
87
+ "import": "./dist/utils/index.js",
88
+ "require": "./dist/utils/index.cjs"
89
89
  },
90
90
  "./package.json": "./package.json"
91
91
  },
@@ -98,19 +98,20 @@
98
98
  "src"
99
99
  ],
100
100
  "dependencies": {
101
- "@tanstack/preact-store": "^0.10.1",
102
- "@tanstack/pacer": "0.17.3"
101
+ "@tanstack/preact-store": "^0.10.2",
102
+ "@tanstack/pacer": "0.19.0"
103
103
  },
104
104
  "devDependencies": {
105
- "@preact/preset-vite": "^2.10.2",
105
+ "@preact/preset-vite": "^2.10.3",
106
106
  "eslint-plugin-react-hooks": "^7.0.1",
107
- "preact": "^10.28.2"
107
+ "preact": "^10.28.3"
108
108
  },
109
109
  "peerDependencies": {
110
110
  "preact": ">=10.0.0"
111
111
  },
112
112
  "scripts": {
113
113
  "clean": "premove ./build ./dist",
114
+ "lint:fix": "eslint ./src --fix",
114
115
  "test:eslint": "eslint ./src",
115
116
  "test:lib": "vitest --passWithNoTests",
116
117
  "test:lib:dev": "pnpm test:lib --watch",
@@ -1,7 +1,6 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import { useAsyncBatcher } from './useAsyncBatcher'
3
- import type { AsyncBatcherOptions } from '@tanstack/pacer/async-batcher'
4
- import type { AnyAsyncFunction } from '@tanstack/pacer/types'
3
+ import type { PreactAsyncBatcherOptions } from './useAsyncBatcher'
5
4
 
6
5
  /**
7
6
  * A Preact hook that creates a batched version of an async callback function.
@@ -40,14 +39,14 @@ import type { AnyAsyncFunction } from '@tanstack/pacer/types'
40
39
  * </button>
41
40
  * ```
42
41
  */
43
- export function useAsyncBatchedCallback<TFn extends AnyAsyncFunction>(
44
- fn: (items: Array<Parameters<TFn>[0]>) => Promise<any>,
45
- options: AsyncBatcherOptions<Parameters<TFn>[0]>,
46
- ): (...args: Parameters<TFn>) => Promise<void> {
42
+ export function useAsyncBatchedCallback<TValue>(
43
+ fn: (items: Array<TValue>) => Promise<unknown>,
44
+ options: PreactAsyncBatcherOptions<TValue, {}>,
45
+ ): (item: TValue) => Promise<void> {
47
46
  const asyncBatchedFn = useAsyncBatcher(fn, options).addItem
48
47
  return useCallback(
49
- async (...args: Parameters<TFn>) => {
50
- asyncBatchedFn(args[0])
48
+ async (item: TValue) => {
49
+ await asyncBatchedFn(item)
51
50
  },
52
51
  [asyncBatchedFn],
53
52
  )
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'preact/hooks'
1
+ import { useEffect, useMemo, useState } from 'preact/hooks'
2
2
  import { AsyncBatcher } from '@tanstack/pacer/async-batcher'
3
3
  import { useStore } from '@tanstack/preact-store'
4
4
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
@@ -9,6 +9,17 @@ import type {
9
9
  } from '@tanstack/pacer/async-batcher'
10
10
  import type { ComponentChildren } from 'preact'
11
11
 
12
+ export interface PreactAsyncBatcherOptions<
13
+ TValue,
14
+ TSelected = {},
15
+ > extends AsyncBatcherOptions<TValue> {
16
+ /**
17
+ * Optional callback invoked when the component unmounts. Receives the batcher instance.
18
+ * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), reset(), cancel(), add logging, etc.
19
+ */
20
+ onUnmount?: (batcher: PreactAsyncBatcher<TValue, TSelected>) => void
21
+ }
22
+
12
23
  export interface PreactAsyncBatcher<TValue, TSelected = {}> extends Omit<
13
24
  AsyncBatcher<TValue>,
14
25
  'store'
@@ -110,6 +121,25 @@ export interface PreactAsyncBatcher<TValue, TSelected = {}> extends Omit<
110
121
  * - `totalItemsProcessed`: Total number of items processed across all batches
111
122
  * - `totalItemsFailed`: Total number of items that have failed processing
112
123
  *
124
+ * ## Unmount behavior
125
+ *
126
+ * By default, the hook cancels any pending batch and aborts any in-flight execution when the component unmounts.
127
+ * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.
128
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
129
+ *
130
+ * ```tsx
131
+ * const batcher = useAsyncBatcher(fn, {
132
+ * maxSize: 10,
133
+ * wait: 2000,
134
+ * onUnmount: (b) => b.flush()
135
+ * });
136
+ * ```
137
+ *
138
+ * Note: For async utils, `flush()` returns a Promise and runs fire-and-forget in the cleanup.
139
+ * If your batch function updates Preact state, those updates may run after the component has
140
+ * unmounted, which can cause "setState on unmounted component" warnings. Guard your callbacks
141
+ * accordingly when using onUnmount with flush.
142
+ *
113
143
  * @example
114
144
  * ```tsx
115
145
  * // Basic async batcher for API requests - no reactive state subscriptions
@@ -204,15 +234,14 @@ export interface PreactAsyncBatcher<TValue, TSelected = {}> extends Omit<
204
234
  */
205
235
  export function useAsyncBatcher<TValue, TSelected = {}>(
206
236
  fn: (items: Array<TValue>) => Promise<any>,
207
- options: AsyncBatcherOptions<TValue> = {},
237
+ options: PreactAsyncBatcherOptions<TValue, TSelected> = {},
208
238
  selector: (state: AsyncBatcherState<TValue>) => TSelected = () =>
209
239
  ({}) as TSelected,
210
240
  ): PreactAsyncBatcher<TValue, TSelected> {
211
241
  const mergedOptions = {
212
242
  ...useDefaultPacerOptions().asyncBatcher,
213
243
  ...options,
214
- } as AsyncBatcherOptions<TValue>
215
-
244
+ } as PreactAsyncBatcherOptions<TValue, TSelected>
216
245
  const [asyncBatcher] = useState(() => {
217
246
  const batcherInstance = new AsyncBatcher<TValue>(
218
247
  fn,
@@ -236,6 +265,19 @@ export function useAsyncBatcher<TValue, TSelected = {}>(
236
265
  asyncBatcher.fn = fn
237
266
  asyncBatcher.setOptions(mergedOptions)
238
267
 
268
+ /* eslint-disable react-hooks/exhaustive-deps -- cleanup only; runs on unmount */
269
+ useEffect(() => {
270
+ return () => {
271
+ if (mergedOptions.onUnmount) {
272
+ mergedOptions.onUnmount(asyncBatcher)
273
+ } else {
274
+ asyncBatcher.cancel()
275
+ asyncBatcher.abort()
276
+ }
277
+ }
278
+ }, [])
279
+ /* eslint-enable react-hooks/exhaustive-deps */
280
+
239
281
  const state = useStore(asyncBatcher.store, selector)
240
282
 
241
283
  return useMemo(
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import { useAsyncDebouncer } from './useAsyncDebouncer'
3
- import type { AsyncDebouncerOptions } from '@tanstack/pacer/async-debouncer'
3
+ import type { PreactAsyncDebouncerOptions } from './useAsyncDebouncer'
4
4
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
5
5
 
6
6
  /**
@@ -43,7 +43,7 @@ import type { AnyAsyncFunction } from '@tanstack/pacer/types'
43
43
  */
44
44
  export function useAsyncDebouncedCallback<TFn extends AnyAsyncFunction>(
45
45
  fn: TFn,
46
- options: AsyncDebouncerOptions<TFn>,
46
+ options: PreactAsyncDebouncerOptions<TFn, {}>,
47
47
  ): (...args: Parameters<TFn>) => Promise<ReturnType<TFn>> {
48
48
  const asyncDebouncedFn = useAsyncDebouncer(fn, options).maybeExecute
49
49
  return useCallback(
@@ -10,6 +10,17 @@ import type {
10
10
  } from '@tanstack/pacer/async-debouncer'
11
11
  import type { ComponentChildren } from 'preact'
12
12
 
13
+ export interface PreactAsyncDebouncerOptions<
14
+ TFn extends AnyAsyncFunction,
15
+ TSelected = {},
16
+ > extends AsyncDebouncerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component unmounts. Receives the debouncer instance.
19
+ * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), reset(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (debouncer: PreactAsyncDebouncer<TFn, TSelected>) => void
22
+ }
23
+
13
24
  export interface PreactAsyncDebouncer<
14
25
  TFn extends AnyAsyncFunction,
15
26
  TSelected = {},
@@ -101,6 +112,24 @@ export interface PreactAsyncDebouncer<
101
112
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending' | 'executing' | 'settled')
102
113
  * - `successCount`: Number of function executions that have completed successfully
103
114
  *
115
+ * ## Unmount behavior
116
+ *
117
+ * By default, the hook cancels any pending execution and aborts any in-flight execution when the component unmounts.
118
+ * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.
119
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
120
+ *
121
+ * ```tsx
122
+ * const debouncer = useAsyncDebouncer(fn, {
123
+ * wait: 500,
124
+ * onUnmount: (d) => d.flush()
125
+ * });
126
+ * ```
127
+ *
128
+ * Note: For async utils, `flush()` returns a Promise and runs fire-and-forget in the cleanup.
129
+ * If your debounced function updates Preact state, those updates may run after the component has
130
+ * unmounted, which can cause "setState on unmounted component" warnings. Guard your callbacks
131
+ * accordingly when using onUnmount with flush.
132
+ *
104
133
  * @example
105
134
  * ```tsx
106
135
  * // Default behavior - no reactive state subscriptions
@@ -184,15 +213,14 @@ export interface PreactAsyncDebouncer<
184
213
  */
185
214
  export function useAsyncDebouncer<TFn extends AnyAsyncFunction, TSelected = {}>(
186
215
  fn: TFn,
187
- options: AsyncDebouncerOptions<TFn>,
216
+ options: PreactAsyncDebouncerOptions<TFn, TSelected>,
188
217
  selector: (state: AsyncDebouncerState<TFn>) => TSelected = () =>
189
218
  ({}) as TSelected,
190
219
  ): PreactAsyncDebouncer<TFn, TSelected> {
191
220
  const mergedOptions = {
192
221
  ...useDefaultPacerOptions().asyncDebouncer,
193
222
  ...options,
194
- } as AsyncDebouncerOptions<TFn>
195
-
223
+ } as PreactAsyncDebouncerOptions<TFn, TSelected>
196
224
  const [asyncDebouncer] = useState(() => {
197
225
  const debouncerInstance = new AsyncDebouncer<TFn>(
198
226
  fn,
@@ -218,11 +246,18 @@ export function useAsyncDebouncer<TFn extends AnyAsyncFunction, TSelected = {}>(
218
246
 
219
247
  const state = useStore(asyncDebouncer.store, selector)
220
248
 
249
+ /* eslint-disable react-hooks/exhaustive-deps -- cleanup only; runs on unmount */
221
250
  useEffect(() => {
222
251
  return () => {
223
- asyncDebouncer.cancel()
252
+ if (mergedOptions.onUnmount) {
253
+ mergedOptions.onUnmount(asyncDebouncer)
254
+ } else {
255
+ asyncDebouncer.cancel()
256
+ asyncDebouncer.abort()
257
+ }
224
258
  }
225
- }, [asyncDebouncer])
259
+ }, [])
260
+ /* eslint-enable react-hooks/exhaustive-deps */
226
261
 
227
262
  return useMemo(
228
263
  () =>
@@ -1,9 +1,9 @@
1
1
  import { useAsyncQueuer } from './useAsyncQueuer'
2
- import type { PreactAsyncQueuer } from './useAsyncQueuer'
3
2
  import type {
4
- AsyncQueuerOptions,
5
- AsyncQueuerState,
6
- } from '@tanstack/pacer/async-queuer'
3
+ PreactAsyncQueuer,
4
+ PreactAsyncQueuerOptions,
5
+ } from './useAsyncQueuer'
6
+ import type { AsyncQueuerState } from '@tanstack/pacer/async-queuer'
7
7
 
8
8
  /**
9
9
  * A higher-level Preact hook that creates an `AsyncQueuer` instance with built-in state management.
@@ -156,7 +156,7 @@ export function useAsyncQueuedState<
156
156
  >,
157
157
  >(
158
158
  fn: (value: TValue) => Promise<any>,
159
- options: AsyncQueuerOptions<TValue> = {},
159
+ options: PreactAsyncQueuerOptions<TValue, TSelected> = {},
160
160
  selector?: (state: AsyncQueuerState<TValue>) => TSelected,
161
161
  ): [Array<TValue>, PreactAsyncQueuer<TValue, TSelected>] {
162
162
  const asyncQueuer = useAsyncQueuer(fn, options, selector)
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'preact/hooks'
1
+ import { useEffect, useMemo, useState } from 'preact/hooks'
2
2
  import { AsyncQueuer } from '@tanstack/pacer/async-queuer'
3
3
  import { useStore } from '@tanstack/preact-store'
4
4
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
@@ -9,6 +9,17 @@ import type {
9
9
  } from '@tanstack/pacer/async-queuer'
10
10
  import type { ComponentChildren } from 'preact'
11
11
 
12
+ export interface PreactAsyncQueuerOptions<
13
+ TValue,
14
+ TSelected = {},
15
+ > extends AsyncQueuerOptions<TValue> {
16
+ /**
17
+ * Optional callback invoked when the component unmounts. Receives the queuer instance.
18
+ * When provided, replaces the default cleanup (stop + abort); use it to call flush(), flushAsBatch(), stop(), add logging, etc.
19
+ */
20
+ onUnmount?: (queuer: PreactAsyncQueuer<TValue, TSelected>) => void
21
+ }
22
+
12
23
  export interface PreactAsyncQueuer<TValue, TSelected = {}> extends Omit<
13
24
  AsyncQueuer<TValue>,
14
25
  'store'
@@ -105,6 +116,25 @@ export interface PreactAsyncQueuer<TValue, TSelected = {}> extends Omit<
105
116
  * - `status`: Current processing status ('idle' | 'running' | 'stopped')
106
117
  * - `successCount`: Number of task executions that have completed successfully
107
118
  *
119
+ * ## Unmount behavior
120
+ *
121
+ * By default, the hook stops the queuer and aborts any in-flight task executions when the component unmounts.
122
+ * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.
123
+ * Use the `onUnmount` option to customize this. For example, to flush pending items instead:
124
+ *
125
+ * ```tsx
126
+ * const queuer = useAsyncQueuer(fn, {
127
+ * concurrency: 2,
128
+ * started: false,
129
+ * onUnmount: (q) => q.flush()
130
+ * });
131
+ * ```
132
+ *
133
+ * Note: For async utils, `flush()` returns a Promise and runs fire-and-forget in the cleanup.
134
+ * If your task function updates Preact state, those updates may run after the component has
135
+ * unmounted, which can cause "setState on unmounted component" warnings. Guard your callbacks
136
+ * accordingly when using onUnmount with flush.
137
+ *
108
138
  * @example
109
139
  * ```tsx
110
140
  * // Default behavior - no reactive state subscriptions
@@ -204,15 +234,14 @@ export interface PreactAsyncQueuer<TValue, TSelected = {}> extends Omit<
204
234
  */
205
235
  export function useAsyncQueuer<TValue, TSelected = {}>(
206
236
  fn: (value: TValue) => Promise<any>,
207
- options: AsyncQueuerOptions<TValue> = {},
237
+ options: PreactAsyncQueuerOptions<TValue, TSelected> = {},
208
238
  selector: (state: AsyncQueuerState<TValue>) => TSelected = () =>
209
239
  ({}) as TSelected,
210
240
  ): PreactAsyncQueuer<TValue, TSelected> {
211
241
  const mergedOptions = {
212
242
  ...useDefaultPacerOptions().asyncQueuer,
213
243
  ...options,
214
- } as AsyncQueuerOptions<TValue>
215
-
244
+ } as PreactAsyncQueuerOptions<TValue, TSelected>
216
245
  const [asyncQueuer] = useState(() => {
217
246
  const queuerInstance = new AsyncQueuer<TValue>(
218
247
  fn,
@@ -236,6 +265,19 @@ export function useAsyncQueuer<TValue, TSelected = {}>(
236
265
  asyncQueuer.fn = fn
237
266
  asyncQueuer.setOptions(mergedOptions)
238
267
 
268
+ /* eslint-disable react-hooks/exhaustive-deps -- cleanup only; runs on unmount */
269
+ useEffect(() => {
270
+ return () => {
271
+ if (mergedOptions.onUnmount) {
272
+ mergedOptions.onUnmount(asyncQueuer)
273
+ } else {
274
+ asyncQueuer.stop()
275
+ asyncQueuer.abort()
276
+ }
277
+ }
278
+ }, [])
279
+ /* eslint-enable react-hooks/exhaustive-deps */
280
+
239
281
  const state = useStore(asyncQueuer.store, selector)
240
282
 
241
283
  return useMemo(
@@ -1,7 +1,7 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import { useAsyncRateLimiter } from './useAsyncRateLimiter'
3
3
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
4
- import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter'
4
+ import type { PreactAsyncRateLimiterOptions } from './useAsyncRateLimiter'
5
5
 
6
6
  /**
7
7
  * A Preact hook that creates a rate-limited version of an async callback function.
@@ -58,7 +58,7 @@ import type { AsyncRateLimiterOptions } from '@tanstack/pacer/async-rate-limiter
58
58
  */
59
59
  export function useAsyncRateLimitedCallback<TFn extends AnyAsyncFunction>(
60
60
  fn: TFn,
61
- options: AsyncRateLimiterOptions<TFn>,
61
+ options: PreactAsyncRateLimiterOptions<TFn, {}>,
62
62
  ): (...args: Parameters<TFn>) => Promise<ReturnType<TFn>> {
63
63
  const asyncRateLimitedFn = useAsyncRateLimiter(fn, options).maybeExecute
64
64
  return useCallback(
@@ -1,4 +1,4 @@
1
- import { useMemo, useState } from 'preact/hooks'
1
+ import { useEffect, useMemo, useState } from 'preact/hooks'
2
2
  import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter'
3
3
  import { useStore } from '@tanstack/preact-store'
4
4
  import { useDefaultPacerOptions } from '../provider/PacerProvider'
@@ -10,6 +10,17 @@ import type {
10
10
  } from '@tanstack/pacer/async-rate-limiter'
11
11
  import type { ComponentChildren } from 'preact'
12
12
 
13
+ export interface PreactAsyncRateLimiterOptions<
14
+ TFn extends AnyAsyncFunction,
15
+ TSelected = {},
16
+ > extends AsyncRateLimiterOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component unmounts. Receives the rate limiter instance.
19
+ * When provided, replaces the default cleanup (abort); use it to call reset(), add logging, etc.
20
+ */
21
+ onUnmount?: (rateLimiter: PreactAsyncRateLimiter<TFn, TSelected>) => void
22
+ }
23
+
13
24
  export interface PreactAsyncRateLimiter<
14
25
  TFn extends AnyAsyncFunction,
15
26
  TSelected = {},
@@ -103,6 +114,12 @@ export interface PreactAsyncRateLimiter<
103
114
  * - `settleCount`: Number of function executions that have completed (success or error)
104
115
  * - `successCount`: Number of function executions that have completed successfully
105
116
  *
117
+ * ## Unmount behavior
118
+ *
119
+ * By default, the hook aborts any in-flight execution when the component unmounts.
120
+ * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.
121
+ * Use the `onUnmount` option to customize this.
122
+ *
106
123
  * @example
107
124
  * ```tsx
108
125
  * // Default behavior - no reactive state subscriptions
@@ -216,14 +233,14 @@ export function useAsyncRateLimiter<
216
233
  TSelected = {},
217
234
  >(
218
235
  fn: TFn,
219
- options: AsyncRateLimiterOptions<TFn>,
236
+ options: PreactAsyncRateLimiterOptions<TFn, TSelected>,
220
237
  selector: (state: AsyncRateLimiterState<TFn>) => TSelected = () =>
221
238
  ({}) as TSelected,
222
239
  ): PreactAsyncRateLimiter<TFn, TSelected> {
223
240
  const mergedOptions = {
224
241
  ...useDefaultPacerOptions().asyncRateLimiter,
225
242
  ...options,
226
- } as AsyncRateLimiterOptions<TFn>
243
+ } as PreactAsyncRateLimiterOptions<TFn, TSelected>
227
244
 
228
245
  const [asyncRateLimiter] = useState(() => {
229
246
  const rateLimiterInstance = new AsyncRateLimiter<TFn>(
@@ -248,6 +265,18 @@ export function useAsyncRateLimiter<
248
265
  asyncRateLimiter.fn = fn
249
266
  asyncRateLimiter.setOptions(mergedOptions)
250
267
 
268
+ /* eslint-disable react-hooks/exhaustive-deps -- cleanup only; runs on unmount */
269
+ useEffect(() => {
270
+ return () => {
271
+ if (mergedOptions.onUnmount) {
272
+ mergedOptions.onUnmount(asyncRateLimiter)
273
+ } else {
274
+ asyncRateLimiter.abort()
275
+ }
276
+ }
277
+ }, [])
278
+ /* eslint-enable react-hooks/exhaustive-deps */
279
+
251
280
  const state = useStore(asyncRateLimiter.store, selector)
252
281
 
253
282
  return useMemo(
@@ -1,6 +1,6 @@
1
1
  import { useCallback } from 'preact/hooks'
2
2
  import { useAsyncThrottler } from './useAsyncThrottler'
3
- import type { AsyncThrottlerOptions } from '@tanstack/pacer/async-throttler'
3
+ import type { PreactAsyncThrottlerOptions } from './useAsyncThrottler'
4
4
  import type { AnyAsyncFunction } from '@tanstack/pacer/types'
5
5
 
6
6
  /**
@@ -41,7 +41,7 @@ import type { AnyAsyncFunction } from '@tanstack/pacer/types'
41
41
  */
42
42
  export function useAsyncThrottledCallback<TFn extends AnyAsyncFunction>(
43
43
  fn: TFn,
44
- options: AsyncThrottlerOptions<TFn>,
44
+ options: PreactAsyncThrottlerOptions<TFn, {}>,
45
45
  ): (...args: Parameters<TFn>) => Promise<ReturnType<TFn>> {
46
46
  const asyncThrottledFn = useAsyncThrottler(fn, options).maybeExecute
47
47
  return useCallback(
@@ -10,6 +10,17 @@ import type {
10
10
  } from '@tanstack/pacer/async-throttler'
11
11
  import type { ComponentChildren } from 'preact'
12
12
 
13
+ export interface PreactAsyncThrottlerOptions<
14
+ TFn extends AnyAsyncFunction,
15
+ TSelected = {},
16
+ > extends AsyncThrottlerOptions<TFn> {
17
+ /**
18
+ * Optional callback invoked when the component unmounts. Receives the throttler instance.
19
+ * When provided, replaces the default cleanup (cancel + abort); use it to call flush(), reset(), cancel(), add logging, etc.
20
+ */
21
+ onUnmount?: (throttler: PreactAsyncThrottler<TFn, TSelected>) => void
22
+ }
23
+
13
24
  export interface PreactAsyncThrottler<
14
25
  TFn extends AnyAsyncFunction,
15
26
  TSelected = {},
@@ -99,6 +110,24 @@ export interface PreactAsyncThrottler<
99
110
  * - `status`: Current execution status ('disabled' | 'idle' | 'pending' | 'executing' | 'settled')
100
111
  * - `successCount`: Number of function executions that have completed successfully
101
112
  *
113
+ * ## Unmount behavior
114
+ *
115
+ * By default, the hook cancels any pending execution and aborts any in-flight execution when the component unmounts.
116
+ * Abort only cancels underlying operations (e.g. fetch) when the abort signal from `getAbortSignal()` is passed to them.
117
+ * Use the `onUnmount` option to customize this. For example, to flush pending work instead:
118
+ *
119
+ * ```tsx
120
+ * const throttler = useAsyncThrottler(fn, {
121
+ * wait: 1000,
122
+ * onUnmount: (t) => t.flush()
123
+ * });
124
+ * ```
125
+ *
126
+ * Note: For async utils, `flush()` returns a Promise and runs fire-and-forget in the cleanup.
127
+ * If your throttled function updates Preact state, those updates may run after the component has
128
+ * unmounted, which can cause "setState on unmounted component" warnings. Guard your callbacks
129
+ * accordingly when using onUnmount with flush.
130
+ *
102
131
  * @example
103
132
  * ```tsx
104
133
  * // Default behavior - no reactive state subscriptions
@@ -195,15 +224,14 @@ export interface PreactAsyncThrottler<
195
224
  */
196
225
  export function useAsyncThrottler<TFn extends AnyAsyncFunction, TSelected = {}>(
197
226
  fn: TFn,
198
- options: AsyncThrottlerOptions<TFn>,
227
+ options: PreactAsyncThrottlerOptions<TFn, TSelected>,
199
228
  selector: (state: AsyncThrottlerState<TFn>) => TSelected = () =>
200
229
  ({}) as TSelected,
201
230
  ): PreactAsyncThrottler<TFn, TSelected> {
202
231
  const mergedOptions = {
203
232
  ...useDefaultPacerOptions().asyncThrottler,
204
233
  ...options,
205
- } as AsyncThrottlerOptions<TFn>
206
-
234
+ } as PreactAsyncThrottlerOptions<TFn, TSelected>
207
235
  const [asyncThrottler] = useState(() => {
208
236
  const throttlerInstance = new AsyncThrottler<TFn>(
209
237
  fn,
@@ -229,9 +257,18 @@ export function useAsyncThrottler<TFn extends AnyAsyncFunction, TSelected = {}>(
229
257
 
230
258
  const state = useStore(asyncThrottler.store, selector)
231
259
 
260
+ /* eslint-disable react-hooks/exhaustive-deps -- cleanup only; runs on unmount */
232
261
  useEffect(() => {
233
- return () => asyncThrottler.cancel()
234
- }, [asyncThrottler])
262
+ return () => {
263
+ if (mergedOptions.onUnmount) {
264
+ mergedOptions.onUnmount(asyncThrottler)
265
+ } else {
266
+ asyncThrottler.cancel()
267
+ asyncThrottler.abort()
268
+ }
269
+ }
270
+ }, [])
271
+ /* eslint-enable react-hooks/exhaustive-deps */
235
272
 
236
273
  return useMemo(
237
274
  () =>