Skip to content

Commit c047955

Browse files
feat: add util.Subscribe (#121)
* feat: add util.Subscribe * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 3a24db1 commit c047955

File tree

184 files changed

+11746
-6206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

184 files changed

+11746
-6206
lines changed

.changeset/spicy-clowns-eat.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/preact-pacer': minor
3+
'@tanstack/react-pacer': minor
4+
'@tanstack/solid-pacer': minor
5+
---
6+
7+
add Subscribe API to all util hooks

docs/framework/preact/adapter.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,80 @@ import { PacerProvider } from '@tanstack/preact-pacer'
113113

114114
All hooks within the provider will automatically use these default options, which can be overridden on a per-hook basis.
115115

116+
## Subscribing to State
117+
118+
The Preact Adapter supports subscribing to state changes in two ways:
119+
120+
### Using the Subscribe Component
121+
122+
Use the `Subscribe` component to subscribe to state changes deep in your component tree without needing to pass a selector to the hook. This is ideal when you want to subscribe to state in child components.
123+
124+
```tsx
125+
import { useRateLimiter } from '@tanstack/preact-pacer'
126+
127+
function ApiComponent() {
128+
const rateLimiter = useRateLimiter(
129+
(data: string) => {
130+
return fetch('/api/endpoint', {
131+
method: 'POST',
132+
body: JSON.stringify({ data }),
133+
})
134+
},
135+
{ limit: 5, window: 60000 }
136+
)
137+
138+
return (
139+
<div>
140+
<button onClick={() => rateLimiter.maybeExecute('some data')}>
141+
Submit
142+
</button>
143+
144+
<rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
145+
{({ rejectionCount }) => (
146+
<div>Rejections: {rejectionCount}</div>
147+
)}
148+
</rateLimiter.Subscribe>
149+
</div>
150+
)
151+
}
152+
```
153+
154+
### Using the Selector Parameter
155+
156+
The `selector` parameter allows you to specify which state changes will trigger reactive updates at the hook level, optimizing performance by preventing unnecessary updates when irrelevant state changes occur.
157+
158+
**By default, `hook.state` is empty (`{}`) as the selector is empty by default.** You must opt-in to state tracking by providing a selector function.
159+
160+
```tsx
161+
import { useDebouncer } from '@tanstack/preact-pacer'
162+
163+
function SearchComponent() {
164+
// Default behavior - no reactive state subscriptions
165+
const debouncer = useDebouncer(
166+
(query: string) => fetchSearchResults(query),
167+
{ wait: 500 }
168+
)
169+
console.log(debouncer.state) // {}
170+
171+
// Opt-in to track isPending changes
172+
const debouncer = useDebouncer(
173+
(query: string) => fetchSearchResults(query),
174+
{ wait: 500 },
175+
(state) => ({ isPending: state.isPending })
176+
)
177+
console.log(debouncer.state.isPending) // Reactive value
178+
179+
return (
180+
<input
181+
onChange={(e) => debouncer.maybeExecute(e.target.value)}
182+
placeholder="Search..."
183+
/>
184+
)
185+
}
186+
```
187+
188+
For more details on state management and available state properties, see the individual guide pages for each utility (e.g., [Rate Limiting Guide](../../guides/rate-limiting.md), [Debouncing Guide](../../guides/debouncing.md)).
189+
116190
## Examples
117191

118192
### Debouncer Example

docs/framework/preact/reference/functions/useAsyncBatcher.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ title: useAsyncBatcher
99
function useAsyncBatcher<TValue, TSelected>(
1010
fn,
1111
options,
12-
selector): ReactAsyncBatcher<TValue, TSelected>;
12+
selector): PreactAsyncBatcher<TValue, TSelected>;
1313
```
1414

15-
Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:170](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L170)
15+
Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:205](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L205)
1616

1717
A Preact hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items.
1818

@@ -44,14 +44,24 @@ Error Handling:
4444

4545
## State Management and Selector
4646

47-
The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
48-
to specify which state changes will trigger a re-render, optimizing performance by preventing
49-
unnecessary re-renders when irrelevant state changes occur.
47+
The hook uses TanStack Store for reactive state management. You can subscribe to state changes
48+
in two ways:
49+
50+
**1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)**
51+
52+
Use the `Subscribe` HOC to subscribe to state changes deep in your component tree without
53+
needing to pass a selector to the hook. This is ideal when you want to subscribe to state
54+
in child components.
55+
56+
**2. Using the `selector` parameter (For hook-level subscriptions)**
57+
58+
The `selector` parameter allows you to specify which state changes will trigger a re-render
59+
at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant
60+
state changes occur.
5061

5162
**By default, there will be no reactive state subscriptions** and you must opt-in to state
52-
tracking by providing a selector function. This prevents unnecessary re-renders and gives you
53-
full control over when your component updates. Only when you provide a selector will the
54-
component re-render when the selected state values change.
63+
tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary
64+
re-renders and gives you full control over when your component updates.
5565

5666
Available state properties:
5767
- `errorCount`: Number of batch executions that have resulted in errors
@@ -96,7 +106,7 @@ Available state properties:
96106

97107
## Returns
98108

99-
[`ReactAsyncBatcher`](../interfaces/ReactAsyncBatcher.md)\<`TValue`, `TSelected`\>
109+
[`PreactAsyncBatcher`](../interfaces/PreactAsyncBatcher.md)\<`TValue`, `TSelected`\>
100110

101111
## Example
102112

@@ -110,7 +120,14 @@ const asyncBatcher = useAsyncBatcher(
110120
{ maxSize: 10, wait: 2000 }
111121
);
112122

113-
// Opt-in to re-render when execution state changes (optimized for loading indicators)
123+
// Subscribe to state changes deep in component tree using Subscribe HOC
124+
<asyncBatcher.Subscribe selector={(state) => ({ size: state.size })}>
125+
{({ size }) => (
126+
<div>Batch Size: {size}</div>
127+
)}
128+
</asyncBatcher.Subscribe>
129+
130+
// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators)
114131
const asyncBatcher = useAsyncBatcher(
115132
async (items) => {
116133
const results = await Promise.all(items.map(item => processItem(item)));

docs/framework/preact/reference/functions/useAsyncDebouncer.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ title: useAsyncDebouncer
99
function useAsyncDebouncer<TFn, TSelected>(
1010
fn,
1111
options,
12-
selector): ReactAsyncDebouncer<TFn, TSelected>;
12+
selector): PreactAsyncDebouncer<TFn, TSelected>;
1313
```
1414

15-
Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L150)
15+
Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:185](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L185)
1616

1717
A low-level Preact hook that creates an `AsyncDebouncer` instance to delay execution of an async function.
1818

@@ -39,14 +39,24 @@ Error Handling:
3939

4040
## State Management and Selector
4141

42-
The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
43-
to specify which state changes will trigger a re-render, optimizing performance by preventing
44-
unnecessary re-renders when irrelevant state changes occur.
42+
The hook uses TanStack Store for reactive state management. You can subscribe to state changes
43+
in two ways:
44+
45+
**1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)**
46+
47+
Use the `Subscribe` HOC to subscribe to state changes deep in your component tree without
48+
needing to pass a selector to the hook. This is ideal when you want to subscribe to state
49+
in child components.
50+
51+
**2. Using the `selector` parameter (For hook-level subscriptions)**
52+
53+
The `selector` parameter allows you to specify which state changes will trigger a re-render
54+
at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant
55+
state changes occur.
4556

4657
**By default, there will be no reactive state subscriptions** and you must opt-in to state
47-
tracking by providing a selector function. This prevents unnecessary re-renders and gives you
48-
full control over when your component updates. Only when you provide a selector will the
49-
component re-render when the selected state values change.
58+
tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary
59+
re-renders and gives you full control over when your component updates.
5060

5161
Available state properties:
5262
- `canLeadingExecute`: Whether the debouncer can execute on the leading edge
@@ -86,7 +96,7 @@ Available state properties:
8696

8797
## Returns
8898

89-
[`ReactAsyncDebouncer`](../interfaces/ReactAsyncDebouncer.md)\<`TFn`, `TSelected`\>
99+
[`PreactAsyncDebouncer`](../interfaces/PreactAsyncDebouncer.md)\<`TFn`, `TSelected`\>
90100

91101
## Example
92102

@@ -100,7 +110,14 @@ const searchDebouncer = useAsyncDebouncer(
100110
{ wait: 500 }
101111
);
102112

103-
// Opt-in to re-render when execution state changes (optimized for loading indicators)
113+
// Subscribe to state changes deep in component tree using Subscribe HOC
114+
<searchDebouncer.Subscribe selector={(state) => ({ isPending: state.isPending })}>
115+
{({ isPending }) => (
116+
<div>{isPending ? 'Searching...' : 'Ready'}</div>
117+
)}
118+
</searchDebouncer.Subscribe>
119+
120+
// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators)
104121
const searchDebouncer = useAsyncDebouncer(
105122
async (query: string) => {
106123
const results = await api.search(query);

docs/framework/preact/reference/functions/useAsyncQueuedState.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ title: useAsyncQueuedState
99
function useAsyncQueuedState<TValue, TSelected>(
1010
fn,
1111
options,
12-
selector?): [TValue[], ReactAsyncQueuer<TValue, TSelected>];
12+
selector?): [TValue[], PreactAsyncQueuer<TValue, TSelected>];
1313
```
1414

1515
Defined in: [preact-pacer/src/async-queuer/useAsyncQueuedState.ts:151](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuedState.ts#L151)
@@ -88,7 +88,7 @@ Available async queuer state properties:
8888

8989
## Returns
9090

91-
\[`TValue`[], [`ReactAsyncQueuer`](../interfaces/ReactAsyncQueuer.md)\<`TValue`, `TSelected`\>\]
91+
\[`TValue`[], [`PreactAsyncQueuer`](../interfaces/PreactAsyncQueuer.md)\<`TValue`, `TSelected`\>\]
9292

9393
## Example
9494

docs/framework/preact/reference/functions/useAsyncQueuer.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ title: useAsyncQueuer
99
function useAsyncQueuer<TValue, TSelected>(
1010
fn,
1111
options,
12-
selector): ReactAsyncQueuer<TValue, TSelected>;
12+
selector): PreactAsyncQueuer<TValue, TSelected>;
1313
```
1414

15-
Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:170](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L170)
15+
Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:205](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L205)
1616

1717
A lower-level Preact hook that creates an `AsyncQueuer` instance for managing an async queue of items.
1818

@@ -37,14 +37,24 @@ Error Handling:
3737

3838
## State Management and Selector
3939

40-
The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
41-
to specify which state changes will trigger a re-render, optimizing performance by preventing
42-
unnecessary re-renders when irrelevant state changes occur.
40+
The hook uses TanStack Store for reactive state management. You can subscribe to state changes
41+
in two ways:
42+
43+
**1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)**
44+
45+
Use the `Subscribe` HOC to subscribe to state changes deep in your component tree without
46+
needing to pass a selector to the hook. This is ideal when you want to subscribe to state
47+
in child components.
48+
49+
**2. Using the `selector` parameter (For hook-level subscriptions)**
50+
51+
The `selector` parameter allows you to specify which state changes will trigger a re-render
52+
at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant
53+
state changes occur.
4354

4455
**By default, there will be no reactive state subscriptions** and you must opt-in to state
45-
tracking by providing a selector function. This prevents unnecessary re-renders and gives you
46-
full control over when your component updates. Only when you provide a selector will the
47-
component re-render when the selected state values change.
56+
tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary
57+
re-renders and gives you full control over when your component updates.
4858

4959
Available state properties:
5060
- `activeItems`: Items currently being processed by the queuer
@@ -91,7 +101,7 @@ Available state properties:
91101

92102
## Returns
93103

94-
[`ReactAsyncQueuer`](../interfaces/ReactAsyncQueuer.md)\<`TValue`, `TSelected`\>
104+
[`PreactAsyncQueuer`](../interfaces/PreactAsyncQueuer.md)\<`TValue`, `TSelected`\>
95105

96106
## Example
97107

@@ -105,7 +115,14 @@ const asyncQueuer = useAsyncQueuer(
105115
{ concurrency: 2, maxSize: 100, started: false }
106116
);
107117

108-
// Opt-in to re-render when queue size changes (optimized for displaying queue length)
118+
// Subscribe to state changes deep in component tree using Subscribe HOC
119+
<asyncQueuer.Subscribe selector={(state) => ({ size: state.size })}>
120+
{({ size }) => (
121+
<div>Queue Size: {size}</div>
122+
)}
123+
</asyncQueuer.Subscribe>
124+
125+
// Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length)
109126
const asyncQueuer = useAsyncQueuer(
110127
async (item) => {
111128
const result = await processItem(item);

docs/framework/preact/reference/functions/useAsyncRateLimiter.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ title: useAsyncRateLimiter
99
function useAsyncRateLimiter<TFn, TSelected>(
1010
fn,
1111
options,
12-
selector): ReactAsyncRateLimiter<TFn, TSelected>;
12+
selector): PreactAsyncRateLimiter<TFn, TSelected>;
1313
```
1414

15-
Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L179)
15+
Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:214](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L214)
1616

1717
A low-level Preact hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window.
1818

@@ -43,14 +43,24 @@ Error Handling:
4343

4444
## State Management and Selector
4545

46-
The hook uses TanStack Store for reactive state management. The `selector` parameter allows you
47-
to specify which state changes will trigger a re-render, optimizing performance by preventing
48-
unnecessary re-renders when irrelevant state changes occur.
46+
The hook uses TanStack Store for reactive state management. You can subscribe to state changes
47+
in two ways:
48+
49+
**1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)**
50+
51+
Use the `Subscribe` HOC to subscribe to state changes deep in your component tree without
52+
needing to pass a selector to the hook. This is ideal when you want to subscribe to state
53+
in child components.
54+
55+
**2. Using the `selector` parameter (For hook-level subscriptions)**
56+
57+
The `selector` parameter allows you to specify which state changes will trigger a re-render
58+
at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant
59+
state changes occur.
4960

5061
**By default, there will be no reactive state subscriptions** and you must opt-in to state
51-
tracking by providing a selector function. This prevents unnecessary re-renders and gives you
52-
full control over when your component updates. Only when you provide a selector will the
53-
component re-render when the selected state values change.
62+
tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary
63+
re-renders and gives you full control over when your component updates.
5464

5565
Available state properties:
5666
- `errorCount`: Number of function executions that have resulted in errors
@@ -88,7 +98,7 @@ Available state properties:
8898

8999
## Returns
90100

91-
[`ReactAsyncRateLimiter`](../interfaces/ReactAsyncRateLimiter.md)\<`TFn`, `TSelected`\>
101+
[`PreactAsyncRateLimiter`](../interfaces/PreactAsyncRateLimiter.md)\<`TFn`, `TSelected`\>
92102

93103
## Example
94104

@@ -102,7 +112,14 @@ const asyncRateLimiter = useAsyncRateLimiter(
102112
{ limit: 5, window: 1000 } // 5 calls per second
103113
);
104114

105-
// Opt-in to re-render when execution state changes (optimized for loading indicators)
115+
// Subscribe to state changes deep in component tree using Subscribe HOC
116+
<asyncRateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
117+
{({ rejectionCount }) => (
118+
<div>Rejections: {rejectionCount}</div>
119+
)}
120+
</asyncRateLimiter.Subscribe>
121+
122+
// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators)
106123
const asyncRateLimiter = useAsyncRateLimiter(
107124
async (id: string) => {
108125
const data = await api.fetchData(id);

0 commit comments

Comments
 (0)