From 7913c4b4f1f2c1a1a673b810a9b185119385a26a Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Sun, 21 Dec 2025 12:58:37 -0600 Subject: [PATCH 1/2] feat: add util.Subscribe --- .changeset/spicy-clowns-eat.md | 7 + docs/framework/preact/adapter.md | 74 +++ .../reference/functions/useAsyncBatcher.md | 37 +- .../reference/functions/useAsyncDebouncer.md | 37 +- .../functions/useAsyncQueuedState.md | 4 +- .../reference/functions/useAsyncQueuer.md | 37 +- .../functions/useAsyncRateLimiter.md | 37 +- .../reference/functions/useAsyncThrottler.md | 37 +- .../preact/reference/functions/useBatcher.md | 33 +- .../reference/functions/useDebouncer.md | 33 +- .../preact/reference/functions/useQueuer.md | 33 +- .../reference/functions/useRateLimiter.md | 33 +- .../reference/functions/useThrottler.md | 33 +- docs/framework/preact/reference/index.md | 10 +- .../interfaces/PreactAsyncBatcher.md | 100 ++++ .../interfaces/PreactAsyncDebouncer.md | 100 ++++ .../reference/interfaces/PreactAsyncQueuer.md | 100 ++++ .../interfaces/PreactAsyncRateLimiter.md | 100 ++++ .../interfaces/PreactAsyncThrottler.md | 100 ++++ .../reference/interfaces/PreactBatcher.md | 53 +- .../reference/interfaces/PreactDebouncer.md | 53 +- .../reference/interfaces/PreactQueuer.md | 53 +- .../reference/interfaces/PreactRateLimiter.md | 53 +- .../reference/interfaces/PreactThrottler.md | 53 +- .../reference/interfaces/ReactAsyncBatcher.md | 53 -- .../interfaces/ReactAsyncDebouncer.md | 53 -- .../reference/interfaces/ReactAsyncQueuer.md | 53 -- .../interfaces/ReactAsyncRateLimiter.md | 53 -- .../interfaces/ReactAsyncThrottler.md | 53 -- docs/framework/react/adapter.md | 74 +++ .../reference/functions/useAsyncBatcher.md | 33 +- .../reference/functions/useAsyncDebouncer.md | 33 +- .../reference/functions/useAsyncQueuer.md | 33 +- .../functions/useAsyncRateLimiter.md | 33 +- .../reference/functions/useAsyncThrottler.md | 33 +- .../react/reference/functions/useBatcher.md | 33 +- .../react/reference/functions/useDebouncer.md | 33 +- .../react/reference/functions/useQueuer.md | 33 +- .../reference/functions/useRateLimiter.md | 33 +- .../react/reference/functions/useThrottler.md | 33 +- .../reference/interfaces/ReactAsyncBatcher.md | 53 +- .../interfaces/ReactAsyncDebouncer.md | 53 +- .../reference/interfaces/ReactAsyncQueuer.md | 53 +- .../interfaces/ReactAsyncRateLimiter.md | 53 +- .../interfaces/ReactAsyncThrottler.md | 53 +- .../reference/interfaces/ReactBatcher.md | 53 +- .../reference/interfaces/ReactDebouncer.md | 53 +- .../react/reference/interfaces/ReactQueuer.md | 53 +- .../reference/interfaces/ReactRateLimiter.md | 53 +- .../reference/interfaces/ReactThrottler.md | 53 +- docs/framework/solid/adapter.md | 286 ++++++++++- .../reference/functions/createAsyncBatcher.md | 28 +- .../functions/createAsyncDebouncer.md | 28 +- .../reference/functions/createAsyncQueuer.md | 28 +- .../functions/createAsyncRateLimiter.md | 109 +++- .../functions/createAsyncThrottler.md | 28 +- .../reference/functions/createBatcher.md | 28 +- .../reference/functions/createDebouncer.md | 30 +- .../reference/functions/createQueuedSignal.md | 16 +- .../solid/reference/functions/createQueuer.md | 28 +- .../reference/functions/createRateLimiter.md | 91 +++- .../functions/createThrottledSignal.md | 2 +- .../functions/createThrottledValue.md | 2 +- .../reference/functions/createThrottler.md | 37 +- .../reference/interfaces/SolidAsyncBatcher.md | 51 +- .../interfaces/SolidAsyncDebouncer.md | 51 +- .../reference/interfaces/SolidAsyncQueuer.md | 51 +- .../interfaces/SolidAsyncRateLimiter.md | 51 +- .../interfaces/SolidAsyncThrottler.md | 51 +- .../reference/interfaces/SolidBatcher.md | 51 +- .../reference/interfaces/SolidDebouncer.md | 51 +- .../solid/reference/interfaces/SolidQueuer.md | 51 +- .../reference/interfaces/SolidRateLimiter.md | 51 +- .../reference/interfaces/SolidThrottler.md | 51 +- docs/guides/async-batching.md | 22 +- docs/guides/async-debouncing.md | 24 +- docs/guides/async-queuing.md | 22 +- docs/guides/async-rate-limiting.md | 24 +- docs/guides/async-throttling.md | 24 +- docs/guides/batching.md | 22 +- docs/guides/debouncing.md | 22 +- docs/guides/queuing.md | 22 +- docs/guides/rate-limiting.md | 22 +- docs/guides/throttling.md | 22 +- examples/preact/useAsyncBatcher/src/index.tsx | 214 ++++---- .../preact/useAsyncDebouncer/src/index.tsx | 80 +-- .../preact/useAsyncQueuedState/src/index.tsx | 234 +++++---- examples/preact/useAsyncQueuer/src/index.tsx | 245 ++++----- .../preact/useAsyncRateLimiter/src/index.tsx | 130 ++--- .../src/index.tsx | 96 ++-- .../preact/useAsyncThrottler/src/index.tsx | 78 +-- examples/preact/useBatcher/src/index.tsx | 126 ++--- .../preact/useDebouncedState/src/index.tsx | 226 ++++---- .../preact/useDebouncedValue/src/index.tsx | 141 ++--- examples/preact/useDebouncer/src/index.tsx | 213 ++++---- examples/preact/useQueuedState/src/index.tsx | 294 ++++++----- examples/preact/useQueuedValue/src/index.tsx | 296 ++++++----- examples/preact/useQueuer/src/index.tsx | 321 ++++++------ .../useQueuerWithPersister/src/index.tsx | 285 ++++++----- .../preact/useRateLimitedState/src/index.tsx | 223 ++++---- .../preact/useRateLimitedValue/src/index.tsx | 184 +++---- examples/preact/useRateLimiter/src/index.tsx | 344 +++++++------ .../useRateLimiterWithPersister/src/index.tsx | 277 +++++----- .../preact/useThrottledState/src/index.tsx | 164 +++--- .../preact/useThrottledValue/src/index.tsx | 107 ++-- examples/preact/useThrottler/src/index.tsx | 202 ++++---- examples/preact/util-comparison/src/index.tsx | 484 ++++++++++-------- .../src/index.tsx | 2 + .../react-query-queued-prefetch/src/index.tsx | 2 + .../src/index.tsx | 2 + examples/react/useAsyncBatcher/src/index.tsx | 100 ++-- .../react/useAsyncDebouncer/src/index.tsx | 50 +- .../react/useAsyncQueuedState/src/index.tsx | 183 ++++--- examples/react/useAsyncQueuer/src/index.tsx | 199 +++---- .../react/useAsyncRateLimiter/src/index.tsx | 96 ++-- .../src/index.tsx | 96 ++-- .../react/useAsyncThrottler/src/index.tsx | 52 +- examples/react/useBatcher/src/index.tsx | 110 ++-- .../react/useDebouncedState/src/index.tsx | 226 ++++---- .../react/useDebouncedValue/src/index.tsx | 102 ++-- examples/react/useDebouncer/src/index.tsx | 177 ++++--- examples/react/useQueuedState/src/index.tsx | 279 +++++----- examples/react/useQueuedValue/src/index.tsx | 276 +++++----- examples/react/useQueuer/src/index.tsx | 287 ++++++----- .../useQueuerWithPersister/src/index.tsx | 282 +++++----- .../react/useRateLimitedState/src/index.tsx | 223 ++++---- .../react/useRateLimitedValue/src/index.tsx | 119 +++-- examples/react/useRateLimiter/src/index.tsx | 273 +++++----- .../useRateLimiterWithPersister/src/index.tsx | 274 +++++----- .../react/useThrottledState/src/index.tsx | 164 +++--- .../react/useThrottledValue/src/index.tsx | 86 ++-- examples/react/useThrottler/src/index.tsx | 160 +++--- examples/react/util-comparison/src/index.tsx | 484 ++++++++++-------- .../solid/createAsyncBatcher/src/index.tsx | 165 +++--- .../solid/createAsyncDebouncer/src/index.tsx | 53 +- .../solid/createAsyncQueuer/src/index.tsx | 185 ++++--- .../createAsyncRateLimiter/src/index.tsx | 48 +- .../solid/createAsyncThrottler/src/index.tsx | 41 +- examples/solid/createBatcher/src/index.tsx | 108 ++-- .../solid/createDebouncedSignal/src/index.tsx | 111 ++-- .../solid/createDebouncedValue/src/index.tsx | 63 ++- examples/solid/createDebouncer/src/index.tsx | 221 +++++--- .../solid/createQueuedSignal/src/index.tsx | 262 ++++++---- examples/solid/createQueuer/src/index.tsx | 448 ++++++++-------- .../createRateLimitedSignal/src/index.tsx | 165 +++--- .../createRateLimitedValue/src/index.tsx | 91 ++-- .../solid/createRateLimiter/src/index.tsx | 257 ++++++---- .../solid/createThrottledSignal/src/index.tsx | 108 ++-- .../solid/createThrottledValue/src/index.tsx | 69 +-- examples/solid/createThrottler/src/index.tsx | 184 ++++--- .../src/async-batcher/useAsyncBatcher.ts | 77 ++- .../src/async-debouncer/useAsyncDebouncer.ts | 77 ++- .../src/async-queuer/useAsyncQueuedState.ts | 4 +- .../src/async-queuer/useAsyncQueuer.ts | 77 ++- .../async-rate-limiter/useAsyncRateLimiter.ts | 77 ++- .../src/async-throttler/useAsyncThrottler.ts | 77 ++- .../preact-pacer/src/batcher/useBatcher.ts | 69 ++- .../src/debouncer/useDebouncer.ts | 69 ++- packages/preact-pacer/src/queuer/useQueuer.ts | 69 ++- .../src/rate-limiter/useRateLimiter.ts | 69 ++- .../src/throttler/useThrottler.ts | 69 ++- .../src/async-batcher/useAsyncBatcher.ts | 71 ++- .../src/async-debouncer/useAsyncDebouncer.ts | 71 ++- .../src/async-queuer/useAsyncQueuer.ts | 71 ++- .../async-rate-limiter/useAsyncRateLimiter.ts | 71 ++- .../src/async-throttler/useAsyncThrottler.ts | 71 ++- .../react-pacer/src/batcher/useBatcher.ts | 69 ++- .../react-pacer/src/debouncer/useDebouncer.ts | 69 ++- packages/react-pacer/src/queuer/useQueuer.ts | 69 ++- .../src/rate-limiter/useRateLimiter.ts | 69 ++- .../react-pacer/src/throttler/useThrottler.ts | 69 ++- .../src/async-batcher/createAsyncBatcher.ts | 61 ++- .../async-debouncer/createAsyncDebouncer.ts | 61 ++- .../src/async-queuer/createAsyncQueuer.ts | 61 ++- .../createAsyncRateLimiter.ts | 142 ++++- .../async-throttler/createAsyncThrottler.ts | 61 ++- .../solid-pacer/src/batcher/createBatcher.ts | 61 ++- .../src/debouncer/createDebouncer.ts | 63 ++- .../src/queuer/createQueuedSignal.ts | 16 +- .../solid-pacer/src/queuer/createQueuer.ts | 61 ++- .../src/rate-limiter/createRateLimiter.ts | 124 ++++- .../src/throttler/createThrottledSignal.ts | 2 +- .../src/throttler/createThrottledValue.ts | 2 +- .../src/throttler/createThrottler.ts | 70 ++- 184 files changed, 11746 insertions(+), 6206 deletions(-) create mode 100644 .changeset/spicy-clowns-eat.md create mode 100644 docs/framework/preact/reference/interfaces/PreactAsyncBatcher.md create mode 100644 docs/framework/preact/reference/interfaces/PreactAsyncDebouncer.md create mode 100644 docs/framework/preact/reference/interfaces/PreactAsyncQueuer.md create mode 100644 docs/framework/preact/reference/interfaces/PreactAsyncRateLimiter.md create mode 100644 docs/framework/preact/reference/interfaces/PreactAsyncThrottler.md delete mode 100644 docs/framework/preact/reference/interfaces/ReactAsyncBatcher.md delete mode 100644 docs/framework/preact/reference/interfaces/ReactAsyncDebouncer.md delete mode 100644 docs/framework/preact/reference/interfaces/ReactAsyncQueuer.md delete mode 100644 docs/framework/preact/reference/interfaces/ReactAsyncRateLimiter.md delete mode 100644 docs/framework/preact/reference/interfaces/ReactAsyncThrottler.md diff --git a/.changeset/spicy-clowns-eat.md b/.changeset/spicy-clowns-eat.md new file mode 100644 index 00000000..81884102 --- /dev/null +++ b/.changeset/spicy-clowns-eat.md @@ -0,0 +1,7 @@ +--- +'@tanstack/preact-pacer': minor +'@tanstack/react-pacer': minor +'@tanstack/solid-pacer': minor +--- + +add Subscribe API to all util hooks diff --git a/docs/framework/preact/adapter.md b/docs/framework/preact/adapter.md index e8e708b5..f5e8f012 100644 --- a/docs/framework/preact/adapter.md +++ b/docs/framework/preact/adapter.md @@ -113,6 +113,80 @@ import { PacerProvider } from '@tanstack/preact-pacer' All hooks within the provider will automatically use these default options, which can be overridden on a per-hook basis. +## Subscribing to State + +The Preact Adapter supports subscribing to state changes in two ways: + +### Using the Subscribe Component + +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. + +```tsx +import { useRateLimiter } from '@tanstack/preact-pacer' + +function ApiComponent() { + const rateLimiter = useRateLimiter( + (data: string) => { + return fetch('/api/endpoint', { + method: 'POST', + body: JSON.stringify({ data }), + }) + }, + { limit: 5, window: 60000 } + ) + + return ( +
+ + + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+
+ ) +} +``` + +### Using the Selector Parameter + +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. + +**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. + +```tsx +import { useDebouncer } from '@tanstack/preact-pacer' + +function SearchComponent() { + // Default behavior - no reactive state subscriptions + const debouncer = useDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 } + ) + console.log(debouncer.state) // {} + + // Opt-in to track isPending changes + const debouncer = useDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 }, + (state) => ({ isPending: state.isPending }) + ) + console.log(debouncer.state.isPending) // Reactive value + + return ( + debouncer.maybeExecute(e.target.value)} + placeholder="Search..." + /> + ) +} +``` + +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)). + ## Examples ### Debouncer Example diff --git a/docs/framework/preact/reference/functions/useAsyncBatcher.md b/docs/framework/preact/reference/functions/useAsyncBatcher.md index 842f9934..09e564bf 100644 --- a/docs/framework/preact/reference/functions/useAsyncBatcher.md +++ b/docs/framework/preact/reference/functions/useAsyncBatcher.md @@ -9,10 +9,10 @@ title: useAsyncBatcher function useAsyncBatcher( fn, options, -selector): ReactAsyncBatcher; +selector): PreactAsyncBatcher; ``` -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) +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) A Preact hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items. @@ -44,14 +44,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of batch executions that have resulted in errors @@ -96,7 +106,7 @@ Available state properties: ## Returns -[`ReactAsyncBatcher`](../interfaces/ReactAsyncBatcher.md)\<`TValue`, `TSelected`\> +[`PreactAsyncBatcher`](../interfaces/PreactAsyncBatcher.md)\<`TValue`, `TSelected`\> ## Example @@ -110,7 +120,14 @@ const asyncBatcher = useAsyncBatcher( { maxSize: 10, wait: 2000 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size })}> + {({ size }) => ( +
Batch Size: {size}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncBatcher = useAsyncBatcher( async (items) => { const results = await Promise.all(items.map(item => processItem(item))); diff --git a/docs/framework/preact/reference/functions/useAsyncDebouncer.md b/docs/framework/preact/reference/functions/useAsyncDebouncer.md index 1ad48823..8dedfb5c 100644 --- a/docs/framework/preact/reference/functions/useAsyncDebouncer.md +++ b/docs/framework/preact/reference/functions/useAsyncDebouncer.md @@ -9,10 +9,10 @@ title: useAsyncDebouncer function useAsyncDebouncer( fn, options, -selector): ReactAsyncDebouncer; +selector): PreactAsyncDebouncer; ``` -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) +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) A low-level Preact hook that creates an `AsyncDebouncer` instance to delay execution of an async function. @@ -39,14 +39,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -86,7 +96,7 @@ Available state properties: ## Returns -[`ReactAsyncDebouncer`](../interfaces/ReactAsyncDebouncer.md)\<`TFn`, `TSelected`\> +[`PreactAsyncDebouncer`](../interfaces/PreactAsyncDebouncer.md)\<`TFn`, `TSelected`\> ## Example @@ -100,7 +110,14 @@ const searchDebouncer = useAsyncDebouncer( { wait: 500 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Searching...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const searchDebouncer = useAsyncDebouncer( async (query: string) => { const results = await api.search(query); diff --git a/docs/framework/preact/reference/functions/useAsyncQueuedState.md b/docs/framework/preact/reference/functions/useAsyncQueuedState.md index a473950c..623086e8 100644 --- a/docs/framework/preact/reference/functions/useAsyncQueuedState.md +++ b/docs/framework/preact/reference/functions/useAsyncQueuedState.md @@ -9,7 +9,7 @@ title: useAsyncQueuedState function useAsyncQueuedState( fn, options, - selector?): [TValue[], ReactAsyncQueuer]; + selector?): [TValue[], PreactAsyncQueuer]; ``` 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: ## Returns -\[`TValue`[], [`ReactAsyncQueuer`](../interfaces/ReactAsyncQueuer.md)\<`TValue`, `TSelected`\>\] +\[`TValue`[], [`PreactAsyncQueuer`](../interfaces/PreactAsyncQueuer.md)\<`TValue`, `TSelected`\>\] ## Example diff --git a/docs/framework/preact/reference/functions/useAsyncQueuer.md b/docs/framework/preact/reference/functions/useAsyncQueuer.md index c884aabe..e15d7265 100644 --- a/docs/framework/preact/reference/functions/useAsyncQueuer.md +++ b/docs/framework/preact/reference/functions/useAsyncQueuer.md @@ -9,10 +9,10 @@ title: useAsyncQueuer function useAsyncQueuer( fn, options, -selector): ReactAsyncQueuer; +selector): PreactAsyncQueuer; ``` -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) +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) A lower-level Preact hook that creates an `AsyncQueuer` instance for managing an async queue of items. @@ -37,14 +37,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `activeItems`: Items currently being processed by the queuer @@ -91,7 +101,7 @@ Available state properties: ## Returns -[`ReactAsyncQueuer`](../interfaces/ReactAsyncQueuer.md)\<`TValue`, `TSelected`\> +[`PreactAsyncQueuer`](../interfaces/PreactAsyncQueuer.md)\<`TValue`, `TSelected`\> ## Example @@ -105,7 +115,14 @@ const asyncQueuer = useAsyncQueuer( { concurrency: 2, maxSize: 100, started: false } ); -// Opt-in to re-render when queue size changes (optimized for displaying queue length) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size })}> + {({ size }) => ( +
Queue Size: {size}
+ )} +
+ +// Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) const asyncQueuer = useAsyncQueuer( async (item) => { const result = await processItem(item); diff --git a/docs/framework/preact/reference/functions/useAsyncRateLimiter.md b/docs/framework/preact/reference/functions/useAsyncRateLimiter.md index af81481f..dae66e5d 100644 --- a/docs/framework/preact/reference/functions/useAsyncRateLimiter.md +++ b/docs/framework/preact/reference/functions/useAsyncRateLimiter.md @@ -9,10 +9,10 @@ title: useAsyncRateLimiter function useAsyncRateLimiter( fn, options, -selector): ReactAsyncRateLimiter; +selector): PreactAsyncRateLimiter; ``` -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) +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) A low-level Preact hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. @@ -43,14 +43,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of function executions that have resulted in errors @@ -88,7 +98,7 @@ Available state properties: ## Returns -[`ReactAsyncRateLimiter`](../interfaces/ReactAsyncRateLimiter.md)\<`TFn`, `TSelected`\> +[`PreactAsyncRateLimiter`](../interfaces/PreactAsyncRateLimiter.md)\<`TFn`, `TSelected`\> ## Example @@ -102,7 +112,14 @@ const asyncRateLimiter = useAsyncRateLimiter( { limit: 5, window: 1000 } // 5 calls per second ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); diff --git a/docs/framework/preact/reference/functions/useAsyncThrottler.md b/docs/framework/preact/reference/functions/useAsyncThrottler.md index 2da15b0a..36a4e87f 100644 --- a/docs/framework/preact/reference/functions/useAsyncThrottler.md +++ b/docs/framework/preact/reference/functions/useAsyncThrottler.md @@ -9,10 +9,10 @@ title: useAsyncThrottler function useAsyncThrottler( fn, options, -selector): ReactAsyncThrottler; +selector): PreactAsyncThrottler; ``` -Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:161](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L161) +Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:196](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L196) A low-level Preact hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. @@ -36,14 +36,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of function executions that have resulted in errors @@ -84,7 +94,7 @@ Available state properties: ## Returns -[`ReactAsyncThrottler`](../interfaces/ReactAsyncThrottler.md)\<`TFn`, `TSelected`\> +[`PreactAsyncThrottler`](../interfaces/PreactAsyncThrottler.md)\<`TFn`, `TSelected`\> ## Example @@ -98,7 +108,14 @@ const asyncThrottler = useAsyncThrottler( { wait: 1000 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Processing...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncThrottler = useAsyncThrottler( async (id: string) => { const data = await api.fetchData(id); diff --git a/docs/framework/preact/reference/functions/useBatcher.md b/docs/framework/preact/reference/functions/useBatcher.md index 58fd9bef..ea8fdc3a 100644 --- a/docs/framework/preact/reference/functions/useBatcher.md +++ b/docs/framework/preact/reference/functions/useBatcher.md @@ -12,7 +12,7 @@ function useBatcher( selector): PreactBatcher; ``` -Defined in: [preact-pacer/src/batcher/useBatcher.ts:124](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L124) +Defined in: [preact-pacer/src/batcher/useBatcher.ts:159](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L159) A Preact hook that creates and manages a Batcher instance. @@ -27,14 +27,24 @@ The Batcher collects items and processes them in batches based on configurable c ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of batch executions that have been completed @@ -84,7 +94,14 @@ const batcher = useBatcher( { maxSize: 5, wait: 2000 } ); -// Opt-in to re-render when batch size changes (optimized for displaying queue size) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size })}> + {({ size }) => ( +
Batch Size: {size}
+ )} +
+ +// Opt-in to re-render when batch size changes at hook level (optimized for displaying queue size) const batcher = useBatcher( (items) => console.log('Processing batch:', items), { maxSize: 5, wait: 2000 }, diff --git a/docs/framework/preact/reference/functions/useDebouncer.md b/docs/framework/preact/reference/functions/useDebouncer.md index e8ac6452..1783a85b 100644 --- a/docs/framework/preact/reference/functions/useDebouncer.md +++ b/docs/framework/preact/reference/functions/useDebouncer.md @@ -12,7 +12,7 @@ function useDebouncer( selector): PreactDebouncer; ``` -Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:105](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L105) +Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:140](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L140) A Preact hook that creates and manages a Debouncer instance. @@ -30,14 +30,24 @@ timer resets and starts waiting again. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -84,7 +94,14 @@ const searchDebouncer = useDebouncer( { wait: 500 } ); -// Opt-in to re-render when isPending changes (optimized for loading states) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Searching...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when isPending changes at hook level (optimized for loading states) const searchDebouncer = useDebouncer( (query: string) => fetchSearchResults(query), { wait: 500 }, diff --git a/docs/framework/preact/reference/functions/useQueuer.md b/docs/framework/preact/reference/functions/useQueuer.md index 3c49c902..7806e1ba 100644 --- a/docs/framework/preact/reference/functions/useQueuer.md +++ b/docs/framework/preact/reference/functions/useQueuer.md @@ -12,7 +12,7 @@ function useQueuer( selector): PreactQueuer; ``` -Defined in: [preact-pacer/src/queuer/useQueuer.ts:135](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L135) +Defined in: [preact-pacer/src/queuer/useQueuer.ts:170](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L170) A Preact hook that creates and manages a Queuer instance. @@ -32,14 +32,24 @@ By default uses FIFO (First In First Out) behavior, but can be configured for LI ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of items that have been processed by the queuer @@ -93,7 +103,14 @@ const queue = useQueuer( { started: true, wait: 1000 } ); -// Opt-in to re-render when queue size changes (optimized for displaying queue length) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size })}> + {({ size }) => ( +
Queue Size: {size}
+ )} +
+ +// Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) const queue = useQueuer( (item) => console.log('Processing:', item), { started: true, wait: 1000 }, diff --git a/docs/framework/preact/reference/functions/useRateLimiter.md b/docs/framework/preact/reference/functions/useRateLimiter.md index 2c888ec2..67fe7ae5 100644 --- a/docs/framework/preact/reference/functions/useRateLimiter.md +++ b/docs/framework/preact/reference/functions/useRateLimiter.md @@ -12,7 +12,7 @@ function useRateLimiter( selector): PreactRateLimiter; ``` -Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:144](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L144) +Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L179) A low-level Preact hook that creates a `RateLimiter` instance to enforce rate limits on function execution. @@ -36,14 +36,24 @@ For smoother execution patterns: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of function executions that have been completed @@ -96,7 +106,14 @@ const rateLimiter = useRateLimiter(apiCall, { windowType: 'sliding', }); -// Opt-in to re-render when execution count changes (optimized for tracking successful executions) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+ +// Opt-in to re-render when execution count changes at hook level (optimized for tracking successful executions) const rateLimiter = useRateLimiter( apiCall, { diff --git a/docs/framework/preact/reference/functions/useThrottler.md b/docs/framework/preact/reference/functions/useThrottler.md index 45d790a9..b21d5272 100644 --- a/docs/framework/preact/reference/functions/useThrottler.md +++ b/docs/framework/preact/reference/functions/useThrottler.md @@ -12,7 +12,7 @@ function useThrottler( selector): PreactThrottler; ``` -Defined in: [preact-pacer/src/throttler/useThrottler.ts:110](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L110) +Defined in: [preact-pacer/src/throttler/useThrottler.ts:145](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L145) A low-level Preact hook that creates a `Throttler` instance that limits how often the provided function can execute. @@ -26,14 +26,24 @@ expensive operations or UI updates. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of function executions that have been completed @@ -79,7 +89,14 @@ Available state properties: const [value, setValue] = useState(0); const throttler = useThrottler(setValue, { wait: 1000 }); -// Opt-in to re-render when execution count changes (optimized for tracking executions) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Processing...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution count changes at hook level (optimized for tracking executions) const [value, setValue] = useState(0); const throttler = useThrottler( setValue, diff --git a/docs/framework/preact/reference/index.md b/docs/framework/preact/reference/index.md index a68e1f28..30d684a6 100644 --- a/docs/framework/preact/reference/index.md +++ b/docs/framework/preact/reference/index.md @@ -9,16 +9,16 @@ title: "@tanstack/preact-pacer" - [PacerProviderOptions](interfaces/PacerProviderOptions.md) - [PacerProviderProps](interfaces/PacerProviderProps.md) +- [PreactAsyncBatcher](interfaces/PreactAsyncBatcher.md) +- [PreactAsyncDebouncer](interfaces/PreactAsyncDebouncer.md) +- [PreactAsyncQueuer](interfaces/PreactAsyncQueuer.md) +- [PreactAsyncRateLimiter](interfaces/PreactAsyncRateLimiter.md) +- [PreactAsyncThrottler](interfaces/PreactAsyncThrottler.md) - [PreactBatcher](interfaces/PreactBatcher.md) - [PreactDebouncer](interfaces/PreactDebouncer.md) - [PreactQueuer](interfaces/PreactQueuer.md) - [PreactRateLimiter](interfaces/PreactRateLimiter.md) - [PreactThrottler](interfaces/PreactThrottler.md) -- [ReactAsyncBatcher](interfaces/ReactAsyncBatcher.md) -- [ReactAsyncDebouncer](interfaces/ReactAsyncDebouncer.md) -- [ReactAsyncQueuer](interfaces/ReactAsyncQueuer.md) -- [ReactAsyncRateLimiter](interfaces/ReactAsyncRateLimiter.md) -- [ReactAsyncThrottler](interfaces/ReactAsyncThrottler.md) ## Functions diff --git a/docs/framework/preact/reference/interfaces/PreactAsyncBatcher.md b/docs/framework/preact/reference/interfaces/PreactAsyncBatcher.md new file mode 100644 index 00000000..fa0c69e9 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/PreactAsyncBatcher.md @@ -0,0 +1,100 @@ +--- +id: PreactAsyncBatcher +title: PreactAsyncBatcher +--- + +# Interface: PreactAsyncBatcher\ + +Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L12) + +## Extends + +- `Omit`\<`AsyncBatcher`\<`TValue`\>, `"store"`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +## Properties + +### state + +```ts +readonly state: Readonly; +``` + +Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:38](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L38) + +Reactive state that will be updated and re-rendered when the batcher state changes + +Use this instead of `batcher.store.state` + +*** + +### ~~store~~ + +```ts +readonly store: Store>>; +``` + +Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:44](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L44) + +#### Deprecated + +Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. +The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. +Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L29) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the async batcher state. + +This is useful for opting into state re-renders for specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ size: state.size })}> + {({ size }) => ( +
Batch Size: {size}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactAsyncDebouncer.md b/docs/framework/preact/reference/interfaces/PreactAsyncDebouncer.md new file mode 100644 index 00000000..68c8d856 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/PreactAsyncDebouncer.md @@ -0,0 +1,100 @@ +--- +id: PreactAsyncDebouncer +title: PreactAsyncDebouncer +--- + +# Interface: PreactAsyncDebouncer\ + +Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L13) + +## Extends + +- `Omit`\<`AsyncDebouncer`\<`TFn`\>, `"store"`\> + +## Type Parameters + +### TFn + +`TFn` *extends* `AnyAsyncFunction` + +### TSelected + +`TSelected` = \{ +\} + +## Properties + +### state + +```ts +readonly state: Readonly; +``` + +Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L39) + +Reactive state that will be updated and re-rendered when the debouncer state changes + +Use this instead of `debouncer.store.state` + +*** + +### ~~store~~ + +```ts +readonly store: Store>>; +``` + +Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L45) + +#### Deprecated + +Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. +The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. +Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the async debouncer state. + +This is useful for opting into state re-renders for specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactAsyncQueuer.md b/docs/framework/preact/reference/interfaces/PreactAsyncQueuer.md new file mode 100644 index 00000000..386ea92d --- /dev/null +++ b/docs/framework/preact/reference/interfaces/PreactAsyncQueuer.md @@ -0,0 +1,100 @@ +--- +id: PreactAsyncQueuer +title: PreactAsyncQueuer +--- + +# Interface: PreactAsyncQueuer\ + +Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L12) + +## Extends + +- `Omit`\<`AsyncQueuer`\<`TValue`\>, `"store"`\> + +## Type Parameters + +### TValue + +`TValue` + +### TSelected + +`TSelected` = \{ +\} + +## Properties + +### state + +```ts +readonly state: Readonly; +``` + +Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L38) + +Reactive state that will be updated and re-rendered when the queuer state changes + +Use this instead of `queuer.store.state` + +*** + +### ~~store~~ + +```ts +readonly store: Store>>; +``` + +Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L44) + +#### Deprecated + +Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. +The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. +Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L29) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the async queuer state. + +This is useful for opting into state re-renders for specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ size: state.size })}> + {({ size }) => ( +
Queue Size: {size}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactAsyncRateLimiter.md b/docs/framework/preact/reference/interfaces/PreactAsyncRateLimiter.md new file mode 100644 index 00000000..6b757536 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/PreactAsyncRateLimiter.md @@ -0,0 +1,100 @@ +--- +id: PreactAsyncRateLimiter +title: PreactAsyncRateLimiter +--- + +# Interface: PreactAsyncRateLimiter\ + +Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L13) + +## Extends + +- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, `"store"`\> + +## Type Parameters + +### TFn + +`TFn` *extends* `AnyAsyncFunction` + +### TSelected + +`TSelected` = \{ +\} + +## Properties + +### state + +```ts +readonly state: Readonly; +``` + +Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L39) + +Reactive state that will be updated and re-rendered when the rate limiter state changes + +Use this instead of `rateLimiter.store.state` + +*** + +### ~~store~~ + +```ts +readonly store: Store>>; +``` + +Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L45) + +#### Deprecated + +Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. +The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. +Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the async rate limiter state. + +This is useful for opting into state re-renders for specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactAsyncThrottler.md b/docs/framework/preact/reference/interfaces/PreactAsyncThrottler.md new file mode 100644 index 00000000..f427d934 --- /dev/null +++ b/docs/framework/preact/reference/interfaces/PreactAsyncThrottler.md @@ -0,0 +1,100 @@ +--- +id: PreactAsyncThrottler +title: PreactAsyncThrottler +--- + +# Interface: PreactAsyncThrottler\ + +Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L13) + +## Extends + +- `Omit`\<`AsyncThrottler`\<`TFn`\>, `"store"`\> + +## Type Parameters + +### TFn + +`TFn` *extends* `AnyAsyncFunction` + +### TSelected + +`TSelected` = \{ +\} + +## Properties + +### state + +```ts +readonly state: Readonly; +``` + +Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L39) + +Reactive state that will be updated and re-rendered when the throttler state changes + +Use this instead of `throttler.store.state` + +*** + +### ~~store~~ + +```ts +readonly store: Store>>; +``` + +Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L45) + +#### Deprecated + +Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. +The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. +Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the async throttler state. + +This is useful for opting into state re-renders for specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactBatcher.md b/docs/framework/preact/reference/interfaces/PreactBatcher.md index 7752fecd..b6f520c5 100644 --- a/docs/framework/preact/reference/interfaces/PreactBatcher.md +++ b/docs/framework/preact/reference/interfaces/PreactBatcher.md @@ -5,7 +5,7 @@ title: PreactBatcher # Interface: PreactBatcher\ -Defined in: [preact-pacer/src/batcher/useBatcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L8) +Defined in: [preact-pacer/src/batcher/useBatcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L9) ## Extends @@ -30,7 +30,7 @@ Defined in: [preact-pacer/src/batcher/useBatcher.ts:8](https://github.com/TanSta readonly state: Readonly; ``` -Defined in: [preact-pacer/src/batcher/useBatcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L17) +Defined in: [preact-pacer/src/batcher/useBatcher.ts:35](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L35) Reactive state that will be updated and re-rendered when the batcher state changes @@ -44,10 +44,57 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [preact-pacer/src/batcher/useBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L23) +Defined in: [preact-pacer/src/batcher/useBatcher.ts:41](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L41) #### Deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/batcher/useBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/batcher/useBatcher.ts#L26) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the batcher state. + +This is useful for opting into state re-renders for specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ size: state.size })}> + {({ size }) => ( +
Batch Size: {size}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactDebouncer.md b/docs/framework/preact/reference/interfaces/PreactDebouncer.md index 0f126943..c61d1fb6 100644 --- a/docs/framework/preact/reference/interfaces/PreactDebouncer.md +++ b/docs/framework/preact/reference/interfaces/PreactDebouncer.md @@ -5,7 +5,7 @@ title: PreactDebouncer # Interface: PreactDebouncer\ -Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L12) +Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:12](https://github.com/T readonly state: Readonly; ``` -Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L21) +Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L39) Reactive state that will be updated and re-rendered when the debouncer state changes @@ -44,10 +44,57 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L27) +Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L45) #### Deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/debouncer/useDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/debouncer/useDebouncer.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + +This is useful for opting into state re-renders for specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactQueuer.md b/docs/framework/preact/reference/interfaces/PreactQueuer.md index 25c93a18..b277df4a 100644 --- a/docs/framework/preact/reference/interfaces/PreactQueuer.md +++ b/docs/framework/preact/reference/interfaces/PreactQueuer.md @@ -5,7 +5,7 @@ title: PreactQueuer # Interface: PreactQueuer\ -Defined in: [preact-pacer/src/queuer/useQueuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L8) +Defined in: [preact-pacer/src/queuer/useQueuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L9) ## Extends @@ -30,7 +30,7 @@ Defined in: [preact-pacer/src/queuer/useQueuer.ts:8](https://github.com/TanStack readonly state: Readonly; ``` -Defined in: [preact-pacer/src/queuer/useQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L17) +Defined in: [preact-pacer/src/queuer/useQueuer.ts:35](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L35) Reactive state that will be updated and re-rendered when the queuer state changes @@ -44,10 +44,57 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [preact-pacer/src/queuer/useQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L23) +Defined in: [preact-pacer/src/queuer/useQueuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L41) #### Deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/queuer/useQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/queuer/useQueuer.ts#L26) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the queuer state. + +This is useful for opting into state re-renders for specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ size: state.size })}> + {({ size }) => ( +
Queue Size: {size}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactRateLimiter.md b/docs/framework/preact/reference/interfaces/PreactRateLimiter.md index 48783a41..e223e1e4 100644 --- a/docs/framework/preact/reference/interfaces/PreactRateLimiter.md +++ b/docs/framework/preact/reference/interfaces/PreactRateLimiter.md @@ -5,7 +5,7 @@ title: PreactRateLimiter # Interface: PreactRateLimiter\ -Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L12) +Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:12](https://github. readonly state: Readonly; ``` -Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L21) +Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L39) Reactive state that will be updated and re-rendered when the rate limiter state changes @@ -44,10 +44,57 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>; ``` -Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L27) +Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L45) #### Deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/rate-limiter/useRateLimiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + +This is useful for opting into state re-renders for specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/PreactThrottler.md b/docs/framework/preact/reference/interfaces/PreactThrottler.md index 4ab56418..02200d1b 100644 --- a/docs/framework/preact/reference/interfaces/PreactThrottler.md +++ b/docs/framework/preact/reference/interfaces/PreactThrottler.md @@ -5,7 +5,7 @@ title: PreactThrottler # Interface: PreactThrottler\ -Defined in: [preact-pacer/src/throttler/useThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L12) +Defined in: [preact-pacer/src/throttler/useThrottler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [preact-pacer/src/throttler/useThrottler.ts:12](https://github.com/T readonly state: Readonly; ``` -Defined in: [preact-pacer/src/throttler/useThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L21) +Defined in: [preact-pacer/src/throttler/useThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L39) Reactive state that will be updated and re-rendered when the throttler state changes @@ -44,10 +44,57 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [preact-pacer/src/throttler/useThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L27) +Defined in: [preact-pacer/src/throttler/useThrottler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L45) #### Deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ComponentChildren; +``` + +Defined in: [preact-pacer/src/throttler/useThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/throttler/useThrottler.ts#L30) + +A Preact HOC (Higher Order Component) that allows you to subscribe to the throttler state. + +This is useful for opting into state re-renders for specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ComponentChildren` \| (`state`) => `ComponentChildren` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ComponentChildren` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/preact/reference/interfaces/ReactAsyncBatcher.md b/docs/framework/preact/reference/interfaces/ReactAsyncBatcher.md deleted file mode 100644 index d796c2bb..00000000 --- a/docs/framework/preact/reference/interfaces/ReactAsyncBatcher.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: ReactAsyncBatcher -title: ReactAsyncBatcher ---- - -# Interface: ReactAsyncBatcher\ - -Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L11) - -## Extends - -- `Omit`\<`AsyncBatcher`\<`TValue`\>, `"store"`\> - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -## Properties - -### state - -```ts -readonly state: Readonly; -``` - -Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L20) - -Reactive state that will be updated and re-rendered when the batcher state changes - -Use this instead of `batcher.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [preact-pacer/src/async-batcher/useAsyncBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts#L26) - -#### Deprecated - -Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. -The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. -Although, you can make the state reactive by using the `useStore` in your own usage. diff --git a/docs/framework/preact/reference/interfaces/ReactAsyncDebouncer.md b/docs/framework/preact/reference/interfaces/ReactAsyncDebouncer.md deleted file mode 100644 index 28c1ab9a..00000000 --- a/docs/framework/preact/reference/interfaces/ReactAsyncDebouncer.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: ReactAsyncDebouncer -title: ReactAsyncDebouncer ---- - -# Interface: ReactAsyncDebouncer\ - -Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L12) - -## Extends - -- `Omit`\<`AsyncDebouncer`\<`TFn`\>, `"store"`\> - -## Type Parameters - -### TFn - -`TFn` *extends* `AnyAsyncFunction` - -### TSelected - -`TSelected` = \{ -\} - -## Properties - -### state - -```ts -readonly state: Readonly; -``` - -Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L21) - -Reactive state that will be updated and re-rendered when the debouncer state changes - -Use this instead of `debouncer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [preact-pacer/src/async-debouncer/useAsyncDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts#L27) - -#### Deprecated - -Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. -The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. -Although, you can make the state reactive by using the `useStore` in your own usage. diff --git a/docs/framework/preact/reference/interfaces/ReactAsyncQueuer.md b/docs/framework/preact/reference/interfaces/ReactAsyncQueuer.md deleted file mode 100644 index 706b43d8..00000000 --- a/docs/framework/preact/reference/interfaces/ReactAsyncQueuer.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: ReactAsyncQueuer -title: ReactAsyncQueuer ---- - -# Interface: ReactAsyncQueuer\ - -Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L11) - -## Extends - -- `Omit`\<`AsyncQueuer`\<`TValue`\>, `"store"`\> - -## Type Parameters - -### TValue - -`TValue` - -### TSelected - -`TSelected` = \{ -\} - -## Properties - -### state - -```ts -readonly state: Readonly; -``` - -Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L20) - -Reactive state that will be updated and re-rendered when the queuer state changes - -Use this instead of `queuer.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [preact-pacer/src/async-queuer/useAsyncQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts#L26) - -#### Deprecated - -Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. -The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. -Although, you can make the state reactive by using the `useStore` in your own usage. diff --git a/docs/framework/preact/reference/interfaces/ReactAsyncRateLimiter.md b/docs/framework/preact/reference/interfaces/ReactAsyncRateLimiter.md deleted file mode 100644 index 32013a45..00000000 --- a/docs/framework/preact/reference/interfaces/ReactAsyncRateLimiter.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: ReactAsyncRateLimiter -title: ReactAsyncRateLimiter ---- - -# Interface: ReactAsyncRateLimiter\ - -Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L12) - -## Extends - -- `Omit`\<`AsyncRateLimiter`\<`TFn`\>, `"store"`\> - -## Type Parameters - -### TFn - -`TFn` *extends* `AnyAsyncFunction` - -### TSelected - -`TSelected` = \{ -\} - -## Properties - -### state - -```ts -readonly state: Readonly; -``` - -Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L21) - -Reactive state that will be updated and re-rendered when the rate limiter state changes - -Use this instead of `rateLimiter.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L27) - -#### Deprecated - -Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. -The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. -Although, you can make the state reactive by using the `useStore` in your own usage. diff --git a/docs/framework/preact/reference/interfaces/ReactAsyncThrottler.md b/docs/framework/preact/reference/interfaces/ReactAsyncThrottler.md deleted file mode 100644 index 34aa7058..00000000 --- a/docs/framework/preact/reference/interfaces/ReactAsyncThrottler.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -id: ReactAsyncThrottler -title: ReactAsyncThrottler ---- - -# Interface: ReactAsyncThrottler\ - -Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L12) - -## Extends - -- `Omit`\<`AsyncThrottler`\<`TFn`\>, `"store"`\> - -## Type Parameters - -### TFn - -`TFn` *extends* `AnyAsyncFunction` - -### TSelected - -`TSelected` = \{ -\} - -## Properties - -### state - -```ts -readonly state: Readonly; -``` - -Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L21) - -Reactive state that will be updated and re-rendered when the throttler state changes - -Use this instead of `throttler.store.state` - -*** - -### ~~store~~ - -```ts -readonly store: Store>>; -``` - -Defined in: [preact-pacer/src/async-throttler/useAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts#L27) - -#### Deprecated - -Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. -The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. -Although, you can make the state reactive by using the `useStore` in your own usage. diff --git a/docs/framework/react/adapter.md b/docs/framework/react/adapter.md index a9b1dcb0..10378311 100644 --- a/docs/framework/react/adapter.md +++ b/docs/framework/react/adapter.md @@ -112,6 +112,80 @@ import { PacerProvider } from '@tanstack/react-pacer' All hooks within the provider will automatically use these default options, which can be overridden on a per-hook basis. +## Subscribing to State + +The React adapter supports subscribing to state changes in two ways: + +### Using the Subscribe Component + +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. + +```tsx +import { useRateLimiter } from '@tanstack/react-pacer' + +function ApiComponent() { + const rateLimiter = useRateLimiter( + (data: string) => { + return fetch('/api/endpoint', { + method: 'POST', + body: JSON.stringify({ data }), + }) + }, + { limit: 5, window: 60000 } + ) + + return ( +
+ + + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+
+ ) +} +``` + +### Using the Selector Parameter + +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. + +**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. + +```tsx +import { useDebouncer } from '@tanstack/react-pacer' + +function SearchComponent() { + // Default behavior - no reactive state subscriptions + const debouncer = useDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 } + ) + console.log(debouncer.state) // {} + + // Opt-in to track isPending changes + const debouncer = useDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 }, + (state) => ({ isPending: state.isPending }) + ) + console.log(debouncer.state.isPending) // Reactive value + + return ( + debouncer.maybeExecute(e.target.value)} + placeholder="Search..." + /> + ) +} +``` + +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)). + ## Examples ### Debouncer Example diff --git a/docs/framework/react/reference/functions/useAsyncBatcher.md b/docs/framework/react/reference/functions/useAsyncBatcher.md index 61a6eda8..c1b1244d 100644 --- a/docs/framework/react/reference/functions/useAsyncBatcher.md +++ b/docs/framework/react/reference/functions/useAsyncBatcher.md @@ -12,7 +12,7 @@ function useAsyncBatcher( selector): ReactAsyncBatcher; ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:170](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L170) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:205](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L205) A React hook that creates an `AsyncBatcher` instance for managing asynchronous batches of items. @@ -44,14 +44,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of batch executions that have resulted in errors @@ -110,7 +120,14 @@ const asyncBatcher = useAsyncBatcher( { maxSize: 10, wait: 2000 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size, isExecuting: state.isExecuting })}> + {({ size, isExecuting }) => ( +
Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncBatcher = useAsyncBatcher( async (items) => { const results = await Promise.all(items.map(item => processItem(item))); diff --git a/docs/framework/react/reference/functions/useAsyncDebouncer.md b/docs/framework/react/reference/functions/useAsyncDebouncer.md index 40986901..be890de2 100644 --- a/docs/framework/react/reference/functions/useAsyncDebouncer.md +++ b/docs/framework/react/reference/functions/useAsyncDebouncer.md @@ -12,7 +12,7 @@ function useAsyncDebouncer( selector): ReactAsyncDebouncer; ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L150) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:185](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L185) A low-level React hook that creates an `AsyncDebouncer` instance to delay execution of an async function. @@ -39,14 +39,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -100,7 +110,14 @@ const searchDebouncer = useAsyncDebouncer( { wait: 500 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isExecuting: state.isExecuting, isPending: state.isPending })}> + {({ isExecuting, isPending }) => ( +
{isExecuting || isPending ? 'Loading...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const searchDebouncer = useAsyncDebouncer( async (query: string) => { const results = await api.search(query); diff --git a/docs/framework/react/reference/functions/useAsyncQueuer.md b/docs/framework/react/reference/functions/useAsyncQueuer.md index 9f8f8fd1..67ae16ab 100644 --- a/docs/framework/react/reference/functions/useAsyncQueuer.md +++ b/docs/framework/react/reference/functions/useAsyncQueuer.md @@ -12,7 +12,7 @@ function useAsyncQueuer( selector): ReactAsyncQueuer; ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:170](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L170) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:205](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L205) A lower-level React hook that creates an `AsyncQueuer` instance for managing an async queue of items. @@ -37,14 +37,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `activeItems`: Items currently being processed by the queuer @@ -105,7 +115,14 @@ const asyncQueuer = useAsyncQueuer( { concurrency: 2, maxSize: 100, started: false } ); -// Opt-in to re-render when queue size changes (optimized for displaying queue length) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+ +// Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) const asyncQueuer = useAsyncQueuer( async (item) => { const result = await processItem(item); diff --git a/docs/framework/react/reference/functions/useAsyncRateLimiter.md b/docs/framework/react/reference/functions/useAsyncRateLimiter.md index ab77b5b3..88d87c43 100644 --- a/docs/framework/react/reference/functions/useAsyncRateLimiter.md +++ b/docs/framework/react/reference/functions/useAsyncRateLimiter.md @@ -12,7 +12,7 @@ function useAsyncRateLimiter( selector): ReactAsyncRateLimiter; ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L179) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:214](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L214) A low-level React hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. @@ -43,14 +43,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of function executions that have resulted in errors @@ -102,7 +112,14 @@ const asyncRateLimiter = useAsyncRateLimiter( { limit: 5, window: 1000 } // 5 calls per second ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + {({ rejectionCount, isExecuting }) => ( +
Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncRateLimiter = useAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); diff --git a/docs/framework/react/reference/functions/useAsyncThrottler.md b/docs/framework/react/reference/functions/useAsyncThrottler.md index ec39e343..23c00ee5 100644 --- a/docs/framework/react/reference/functions/useAsyncThrottler.md +++ b/docs/framework/react/reference/functions/useAsyncThrottler.md @@ -12,7 +12,7 @@ function useAsyncThrottler( selector): ReactAsyncThrottler; ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:161](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L161) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:196](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L196) A low-level React hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. @@ -36,14 +36,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `errorCount`: Number of function executions that have resulted in errors @@ -98,7 +108,14 @@ const asyncThrottler = useAsyncThrottler( { wait: 1000 } ); -// Opt-in to re-render when execution state changes (optimized for loading indicators) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isExecuting: state.isExecuting, isPending: state.isPending })}> + {({ isExecuting, isPending }) => ( +
{isExecuting || isPending ? 'Loading...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) const asyncThrottler = useAsyncThrottler( async (id: string) => { const data = await api.fetchData(id); diff --git a/docs/framework/react/reference/functions/useBatcher.md b/docs/framework/react/reference/functions/useBatcher.md index 3317b825..b745f394 100644 --- a/docs/framework/react/reference/functions/useBatcher.md +++ b/docs/framework/react/reference/functions/useBatcher.md @@ -12,7 +12,7 @@ function useBatcher( selector): ReactBatcher; ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:124](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L124) +Defined in: [react-pacer/src/batcher/useBatcher.ts:159](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L159) A React hook that creates and manages a Batcher instance. @@ -27,14 +27,24 @@ The Batcher collects items and processes them in batches based on configurable c ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of batch executions that have been completed @@ -84,7 +94,14 @@ const batcher = useBatcher( { maxSize: 5, wait: 2000 } ); -// Opt-in to re-render when batch size changes (optimized for displaying queue size) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size, isPending: state.isPending })}> + {({ size, isPending }) => ( +
Batch: {size} items, {isPending ? 'Pending' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when batch size changes at hook level (optimized for displaying queue size) const batcher = useBatcher( (items) => console.log('Processing batch:', items), { maxSize: 5, wait: 2000 }, diff --git a/docs/framework/react/reference/functions/useDebouncer.md b/docs/framework/react/reference/functions/useDebouncer.md index e7a3ab52..3c00d40b 100644 --- a/docs/framework/react/reference/functions/useDebouncer.md +++ b/docs/framework/react/reference/functions/useDebouncer.md @@ -12,7 +12,7 @@ function useDebouncer( selector): ReactDebouncer; ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:105](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L105) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:140](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L140) A React hook that creates and manages a Debouncer instance. @@ -30,14 +30,24 @@ timer resets and starts waiting again. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -84,7 +94,14 @@ const searchDebouncer = useDebouncer( { wait: 500 } ); -// Opt-in to re-render when isPending changes (optimized for loading states) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Searching...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when isPending changes at hook level (optimized for loading states) const searchDebouncer = useDebouncer( (query: string) => fetchSearchResults(query), { wait: 500 }, diff --git a/docs/framework/react/reference/functions/useQueuer.md b/docs/framework/react/reference/functions/useQueuer.md index 287efc56..18f61f69 100644 --- a/docs/framework/react/reference/functions/useQueuer.md +++ b/docs/framework/react/reference/functions/useQueuer.md @@ -12,7 +12,7 @@ function useQueuer( selector): ReactQueuer; ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:135](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L135) +Defined in: [react-pacer/src/queuer/useQueuer.ts:170](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L170) A React hook that creates and manages a Queuer instance. @@ -32,14 +32,24 @@ By default uses FIFO (First In First Out) behavior, but can be configured for LI ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of items that have been processed by the queuer @@ -93,7 +103,14 @@ const queue = useQueuer( { started: true, wait: 1000 } ); -// Opt-in to re-render when queue size changes (optimized for displaying queue length) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+ +// Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) const queue = useQueuer( (item) => console.log('Processing:', item), { started: true, wait: 1000 }, diff --git a/docs/framework/react/reference/functions/useRateLimiter.md b/docs/framework/react/reference/functions/useRateLimiter.md index 1f26b1af..ec4a7d8c 100644 --- a/docs/framework/react/reference/functions/useRateLimiter.md +++ b/docs/framework/react/reference/functions/useRateLimiter.md @@ -12,7 +12,7 @@ function useRateLimiter( selector): ReactRateLimiter; ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:144](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L144) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:179](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L179) A low-level React hook that creates a `RateLimiter` instance to enforce rate limits on function execution. @@ -36,14 +36,24 @@ For smoother execution patterns: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of function executions that have been completed @@ -96,7 +106,14 @@ const rateLimiter = useRateLimiter(apiCall, { windowType: 'sliding', }); -// Opt-in to re-render when execution count changes (optimized for tracking successful executions) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejected: {rejectionCount} requests
+ )} +
+ +// Opt-in to re-render when execution count changes at hook level (optimized for tracking successful executions) const rateLimiter = useRateLimiter( apiCall, { diff --git a/docs/framework/react/reference/functions/useThrottler.md b/docs/framework/react/reference/functions/useThrottler.md index 8809712f..39ff66c9 100644 --- a/docs/framework/react/reference/functions/useThrottler.md +++ b/docs/framework/react/reference/functions/useThrottler.md @@ -12,7 +12,7 @@ function useThrottler( selector): ReactThrottler; ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:110](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L110) +Defined in: [react-pacer/src/throttler/useThrottler.ts:145](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L145) A low-level React hook that creates a `Throttler` instance that limits how often the provided function can execute. @@ -26,14 +26,24 @@ expensive operations or UI updates. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + +Use the `Subscribe` HOC 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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +The `selector` parameter allows you to specify which state changes will trigger a re-render +at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant +state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary +re-renders and gives you full control over when your component updates. Available state properties: - `executionCount`: Number of function executions that have been completed @@ -79,7 +89,14 @@ Available state properties: const [value, setValue] = useState(0); const throttler = useThrottler(setValue, { wait: 1000 }); -// Opt-in to re-render when execution count changes (optimized for tracking executions) +// Subscribe to state changes deep in component tree using Subscribe HOC + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Throttling...' : 'Ready'}
+ )} +
+ +// Opt-in to re-render when execution count changes at hook level (optimized for tracking executions) const [value, setValue] = useState(0); const throttler = useThrottler( setValue, diff --git a/docs/framework/react/reference/interfaces/ReactAsyncBatcher.md b/docs/framework/react/reference/interfaces/ReactAsyncBatcher.md index d4fb153d..15d0132e 100644 --- a/docs/framework/react/reference/interfaces/ReactAsyncBatcher.md +++ b/docs/framework/react/reference/interfaces/ReactAsyncBatcher.md @@ -5,7 +5,7 @@ title: ReactAsyncBatcher # Interface: ReactAsyncBatcher\ -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L11) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L12) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:11](https://github readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L20) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:38](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L38) Reactive state that will be updated and re-rendered when the batcher state changes @@ -44,10 +44,57 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L26) +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:44](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L44) #### Deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/async-batcher/useAsyncBatcher.ts:29](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts#L29) + +A React HOC (Higher Order Component) that allows you to subscribe to the batcher state. + +This is useful for opting into state re-renders for specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ size: state.size, isExecuting: state.isExecuting })}> + {({ size, isExecuting }) => ( +
Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactAsyncDebouncer.md b/docs/framework/react/reference/interfaces/ReactAsyncDebouncer.md index aae35cb2..197b5677 100644 --- a/docs/framework/react/reference/interfaces/ReactAsyncDebouncer.md +++ b/docs/framework/react/reference/interfaces/ReactAsyncDebouncer.md @@ -5,7 +5,7 @@ title: ReactAsyncDebouncer # Interface: ReactAsyncDebouncer\ -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L12) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:12](https://gi readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L21) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L39) Reactive state that will be updated and re-rendered when the debouncer state changes @@ -44,10 +44,57 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L27) +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L45) #### Deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/async-debouncer/useAsyncDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + +This is useful for opting into state re-renders for specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ isExecuting: state.isExecuting })}> + {({ isExecuting }) => ( +
{isExecuting ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactAsyncQueuer.md b/docs/framework/react/reference/interfaces/ReactAsyncQueuer.md index bd6d35b3..a9367286 100644 --- a/docs/framework/react/reference/interfaces/ReactAsyncQueuer.md +++ b/docs/framework/react/reference/interfaces/ReactAsyncQueuer.md @@ -5,7 +5,7 @@ title: ReactAsyncQueuer # Interface: ReactAsyncQueuer\ -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:11](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L11) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L12) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:11](https://github.c readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L20) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:38](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L38) Reactive state that will be updated and re-rendered when the queuer state changes @@ -44,10 +44,57 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L26) +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:44](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L44) #### Deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/async-queuer/useAsyncQueuer.ts:29](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts#L29) + +A React HOC (Higher Order Component) that allows you to subscribe to the queuer state. + +This is useful for opting into state re-renders for specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactAsyncRateLimiter.md b/docs/framework/react/reference/interfaces/ReactAsyncRateLimiter.md index e43f54b7..a65daebc 100644 --- a/docs/framework/react/reference/interfaces/ReactAsyncRateLimiter.md +++ b/docs/framework/react/reference/interfaces/ReactAsyncRateLimiter.md @@ -5,7 +5,7 @@ title: ReactAsyncRateLimiter # Interface: ReactAsyncRateLimiter\ -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L12) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:12](https readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L21) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L39) Reactive state that will be updated and re-rendered when the rate limiter state changes @@ -44,10 +44,57 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L27) +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L45) #### Deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + +This is useful for opting into state re-renders for specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + {({ rejectionCount, isExecuting }) => ( +
Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactAsyncThrottler.md b/docs/framework/react/reference/interfaces/ReactAsyncThrottler.md index 3438117c..58e63848 100644 --- a/docs/framework/react/reference/interfaces/ReactAsyncThrottler.md +++ b/docs/framework/react/reference/interfaces/ReactAsyncThrottler.md @@ -5,7 +5,7 @@ title: ReactAsyncThrottler # Interface: ReactAsyncThrottler\ -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L12) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:12](https://gi readonly state: Readonly; ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L21) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L39) Reactive state that will be updated and re-rendered when the throttler state changes @@ -44,10 +44,57 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L27) +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L45) #### Deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/async-throttler/useAsyncThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the throttler state. + +This is useful for opting into state re-renders for specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ isExecuting: state.isExecuting })}> + {({ isExecuting }) => ( +
{isExecuting ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactBatcher.md b/docs/framework/react/reference/interfaces/ReactBatcher.md index c600dbfa..bff24ee1 100644 --- a/docs/framework/react/reference/interfaces/ReactBatcher.md +++ b/docs/framework/react/reference/interfaces/ReactBatcher.md @@ -5,7 +5,7 @@ title: ReactBatcher # Interface: ReactBatcher\ -Defined in: [react-pacer/src/batcher/useBatcher.ts:8](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L8) +Defined in: [react-pacer/src/batcher/useBatcher.ts:9](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L9) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/batcher/useBatcher.ts:8](https://github.com/TanStac readonly state: Readonly; ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L17) +Defined in: [react-pacer/src/batcher/useBatcher.ts:35](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L35) Reactive state that will be updated and re-rendered when the batcher state changes @@ -44,10 +44,57 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/batcher/useBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L23) +Defined in: [react-pacer/src/batcher/useBatcher.ts:41](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L41) #### Deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/batcher/useBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/batcher/useBatcher.ts#L26) + +A React HOC (Higher Order Component) that allows you to subscribe to the batcher state. + +This is useful for opting into state re-renders for specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ size: state.size, isPending: state.isPending })}> + {({ size, isPending }) => ( +
Batch: {size} items, {isPending ? 'Pending' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactDebouncer.md b/docs/framework/react/reference/interfaces/ReactDebouncer.md index 963360e3..c449de54 100644 --- a/docs/framework/react/reference/interfaces/ReactDebouncer.md +++ b/docs/framework/react/reference/interfaces/ReactDebouncer.md @@ -5,7 +5,7 @@ title: ReactDebouncer # Interface: ReactDebouncer\ -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L12) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/debouncer/useDebouncer.ts:12](https://github.com/Ta readonly state: Readonly; ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L21) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L39) Reactive state that will be updated and re-rendered when the debouncer state changes @@ -44,10 +44,57 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/debouncer/useDebouncer.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L27) +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L45) #### Deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/debouncer/useDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/debouncer/useDebouncer.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + +This is useful for opting into state re-renders for specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactQueuer.md b/docs/framework/react/reference/interfaces/ReactQueuer.md index 96e3923d..a0b5f64f 100644 --- a/docs/framework/react/reference/interfaces/ReactQueuer.md +++ b/docs/framework/react/reference/interfaces/ReactQueuer.md @@ -5,7 +5,7 @@ title: ReactQueuer # Interface: ReactQueuer\ -Defined in: [react-pacer/src/queuer/useQueuer.ts:8](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L8) +Defined in: [react-pacer/src/queuer/useQueuer.ts:9](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L9) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/queuer/useQueuer.ts:8](https://github.com/TanStack/ readonly state: Readonly; ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L17) +Defined in: [react-pacer/src/queuer/useQueuer.ts:35](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L35) Reactive state that will be updated and re-rendered when the queuer state changes @@ -44,10 +44,57 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/queuer/useQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L23) +Defined in: [react-pacer/src/queuer/useQueuer.ts:41](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L41) #### Deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/queuer/useQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/queuer/useQueuer.ts#L26) + +A React HOC (Higher Order Component) that allows you to subscribe to the queuer state. + +This is useful for opting into state re-renders for specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactRateLimiter.md b/docs/framework/react/reference/interfaces/ReactRateLimiter.md index f4b49001..7fcc3713 100644 --- a/docs/framework/react/reference/interfaces/ReactRateLimiter.md +++ b/docs/framework/react/reference/interfaces/ReactRateLimiter.md @@ -5,7 +5,7 @@ title: ReactRateLimiter # Interface: ReactRateLimiter\ -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L12) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:12](https://github.c readonly state: Readonly; ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L21) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L39) Reactive state that will be updated and re-rendered when the rate limiter state changes @@ -44,10 +44,57 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>; ``` -Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L27) +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L45) #### Deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/rate-limiter/useRateLimiter.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/rate-limiter/useRateLimiter.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + +This is useful for opting into state re-renders for specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejected: {rejectionCount} requests
+ )} +
+``` diff --git a/docs/framework/react/reference/interfaces/ReactThrottler.md b/docs/framework/react/reference/interfaces/ReactThrottler.md index 3c60a4cc..6b3b03d8 100644 --- a/docs/framework/react/reference/interfaces/ReactThrottler.md +++ b/docs/framework/react/reference/interfaces/ReactThrottler.md @@ -5,7 +5,7 @@ title: ReactThrottler # Interface: ReactThrottler\ -Defined in: [react-pacer/src/throttler/useThrottler.ts:12](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L12) +Defined in: [react-pacer/src/throttler/useThrottler.ts:13](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L13) ## Extends @@ -30,7 +30,7 @@ Defined in: [react-pacer/src/throttler/useThrottler.ts:12](https://github.com/Ta readonly state: Readonly; ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L21) +Defined in: [react-pacer/src/throttler/useThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L39) Reactive state that will be updated and re-rendered when the throttler state changes @@ -44,10 +44,57 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [react-pacer/src/throttler/useThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L27) +Defined in: [react-pacer/src/throttler/useThrottler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L45) #### Deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => ReactNode | Promise; +``` + +Defined in: [react-pacer/src/throttler/useThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/react-pacer/src/throttler/useThrottler.ts#L30) + +A React HOC (Higher Order Component) that allows you to subscribe to the throttler state. + +This is useful for opting into state re-renders for specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`ReactNode` \| (`state`) => `ReactNode` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`ReactNode` \| `Promise`\<`ReactNode`\> + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Throttling...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/solid/adapter.md b/docs/framework/solid/adapter.md index 439990b9..2c8a0ff9 100644 --- a/docs/framework/solid/adapter.md +++ b/docs/framework/solid/adapter.md @@ -1,8 +1,284 @@ --- title: TanStack Pacer Solid Adapter -ref: docs/framework/react/adapter.md -replace: { - "React": "Solid", - "react": "solid" -} +id: adapter --- + +If you are using TanStack Pacer in a Solid application, we recommend using the Solid Adapter. The Solid Adapter provides a set of easy-to-use hooks on top of the core Pacer utilities. If you find yourself wanting to use the core Pacer classes/functions directly, the Solid Adapter will also re-export everything from the core package. + +## Installation + +```sh +npm install @tanstack/solid-pacer +``` + +## Solid Hooks + +See the [Solid Functions Reference](./reference/index.md) to see the full list of hooks available in the Solid Adapter. + +## Basic Usage + +Import a solid specific hook from the Solid Adapter. + +```tsx +import { createDebouncedValue } from '@tanstack/solid-pacer' +import { createSignal } from 'solid-js' + +const [instantValue, setInstantValue] = createSignal(0) +const [debouncedValue, debouncer] = createDebouncedValue(instantValue, { + wait: 1000, +}) +``` + +Or import a core Pacer class/function that is re-exported from the Solid Adapter. + +```tsx +import { debounce, Debouncer } from '@tanstack/solid-pacer' // no need to install the core package separately +``` + +## Option Helpers + +If you want a type-safe way to define common options for pacer utilities, TanStack Pacer provides option helpers for each utility. These helpers can be used with Solid hooks. + +### Debouncer Options + +```tsx +import { createDebouncer } from '@tanstack/solid-pacer' +import { debouncerOptions } from '@tanstack/pacer' + +const commonDebouncerOptions = debouncerOptions({ + wait: 1000, + leading: false, + trailing: true, +}) + +const debouncer = createDebouncer( + (query: string) => fetchSearchResults(query), + { ...commonDebouncerOptions, key: 'searchDebouncer' } +) +``` + +### Queuer Options + +```tsx +import { createQueuer } from '@tanstack/solid-pacer' +import { queuerOptions } from '@tanstack/pacer' + +const commonQueuerOptions = queuerOptions({ + concurrency: 3, + addItemsTo: 'back', +}) + +const queuer = createQueuer( + (item: string) => processItem(item), + { ...commonQueuerOptions, key: 'itemQueuer' } +) +``` + +### Rate Limiter Options + +```tsx +import { createRateLimiter } from '@tanstack/solid-pacer' +import { rateLimiterOptions } from '@tanstack/pacer' + +const commonRateLimiterOptions = rateLimiterOptions({ + limit: 5, + window: 60000, + windowType: 'sliding', +}) + +const rateLimiter = createRateLimiter( + (data: string) => sendApiRequest(data), + { ...commonRateLimiterOptions, key: 'apiRateLimiter' } +) +``` + +## Provider + +The Solid Adapter provides a `PacerProvider` component that you can use to provide default options to all instances of pacer utilities within your component tree. + +```tsx +import { PacerProvider } from '@tanstack/solid-pacer' + +// Set default options for solid-pacer instances + + + +``` + +All hooks within the provider will automatically use these default options, which can be overridden on a per-hook basis. + +## Subscribing to State + +The Solid Adapter supports subscribing to state changes in two ways: + +### Using the Subscribe Component + +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. + +**Note:** In Solid, the `Subscribe` component provides an accessor (signal) to the selected state. You must call `state()` to access the value. + +```tsx +import { createRateLimiter } from '@tanstack/solid-pacer' + +function ApiComponent() { + const rateLimiter = createRateLimiter( + (data: string) => { + return fetch('/api/endpoint', { + method: 'POST', + body: JSON.stringify({ data }), + }) + }, + { limit: 5, window: 60000 } + ) + + return ( +
+ + + ({ rejectionCount: state.rejectionCount })}> + {(state) => ( +
Rejections: {state().rejectionCount}
+ )} +
+
+ ) +} +``` + +### Using the Selector Parameter + +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. + +**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. + +**Note:** In Solid, `hook.state` is an accessor (signal). You must call `hook.state()` to access the value. + +```tsx +import { createDebouncer } from '@tanstack/solid-pacer' + +function SearchComponent() { + // Default behavior - no reactive state subscriptions + const debouncer = createDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 } + ) + console.log(debouncer.state()) // {} + + // Opt-in to track isPending changes + const debouncer = createDebouncer( + (query: string) => fetchSearchResults(query), + { wait: 500 }, + (state) => ({ isPending: state.isPending }) + ) + console.log(debouncer.state().isPending) // Reactive value + + return ( + debouncer.maybeExecute(e.target.value)} + placeholder="Search..." + /> + ) +} +``` + +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)). + +## Examples + +### Debouncer Example + +```tsx +import { createDebouncer } from '@tanstack/solid-pacer' + +function SearchComponent() { + const debouncer = createDebouncer( + (query: string) => { + console.log('Searching for:', query) + // Perform search + }, + { wait: 500 } + ) + + return ( + debouncer.maybeExecute(e.currentTarget.value)} + placeholder="Search..." + /> + ) +} +``` + +### Queuer Example + +```tsx +import { createQueuer } from '@tanstack/solid-pacer' + +function UploadComponent() { + const queuer = createQueuer( + async (file: File) => { + await uploadFile(file) + }, + { concurrency: 3 } + ) + + const handleFileSelect = (files: FileList) => { + Array.from(files).forEach((file) => { + queuer.addItem(file) + }) + } + + return ( + { + if (e.target.files) { + handleFileSelect(e.target.files) + } + }} + /> + ) +} +``` + +### Rate Limiter Example + +```tsx +import { createRateLimiter } from '@tanstack/solid-pacer' + +function ApiComponent() { + const rateLimiter = createRateLimiter( + (data: string) => { + return fetch('/api/endpoint', { + method: 'POST', + body: JSON.stringify({ data }), + }) + }, + { + limit: 5, + window: 60000, + windowType: 'sliding', + onReject: () => { + alert('Rate limit reached. Please try again later.') + }, + } + ) + + const handleSubmit = () => { + const remaining = rateLimiter.getRemainingInWindow() + if (remaining > 0) { + rateLimiter.maybeExecute('some data') + } + } + + return +} +``` diff --git a/docs/framework/solid/reference/functions/createAsyncBatcher.md b/docs/framework/solid/reference/functions/createAsyncBatcher.md index 7f886af0..a2a0c37a 100644 --- a/docs/framework/solid/reference/functions/createAsyncBatcher.md +++ b/docs/framework/solid/reference/functions/createAsyncBatcher.md @@ -12,7 +12,7 @@ function createAsyncBatcher( selector): SolidAsyncBatcher; ``` -Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:131](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L131) +Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:158](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L158) Creates a Solid-compatible AsyncBatcher instance for managing asynchronous batches of items, exposing Solid signals for all stateful properties. @@ -45,14 +45,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `errorCount`: Number of failed batch executions @@ -85,7 +95,7 @@ const asyncBatcher = createAsyncBatcher( } ); -// Opt-in to re-render when items or isExecuting changes (optimized for UI updates) +// Opt-in to track items or isExecuting changes (optimized for UI updates) const asyncBatcher = createAsyncBatcher( async (items) => { const results = await Promise.all(items.map(item => processItem(item))); @@ -95,7 +105,7 @@ const asyncBatcher = createAsyncBatcher( (state) => ({ items: state.items, isExecuting: state.isExecuting }) ); -// Opt-in to re-render when error state changes (optimized for error handling) +// Opt-in to track error state changes (optimized for error handling) const asyncBatcher = createAsyncBatcher( async (items) => { const results = await Promise.all(items.map(item => processItem(item))); diff --git a/docs/framework/solid/reference/functions/createAsyncDebouncer.md b/docs/framework/solid/reference/functions/createAsyncDebouncer.md index d24cbed1..d0e1dd7a 100644 --- a/docs/framework/solid/reference/functions/createAsyncDebouncer.md +++ b/docs/framework/solid/reference/functions/createAsyncDebouncer.md @@ -12,7 +12,7 @@ function createAsyncDebouncer( selector): SolidAsyncDebouncer; ``` -Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:119](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L119) +Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:146](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L146) A low-level Solid hook that creates an `AsyncDebouncer` instance to delay execution of an async function. @@ -39,14 +39,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -100,7 +110,7 @@ const { maybeExecute } = createAsyncDebouncer( { wait: 500 } ); -// Opt-in to re-render when isPending or isExecuting changes (optimized for loading states) +// Opt-in to track isPending or isExecuting changes (optimized for loading states) const debouncer = createAsyncDebouncer( async (query: string) => { const results = await api.search(query); @@ -110,7 +120,7 @@ const debouncer = createAsyncDebouncer( (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting }) ); -// Opt-in to re-render when error state changes (optimized for error handling) +// Opt-in to track error state changes (optimized for error handling) const debouncer = createAsyncDebouncer( async (searchTerm) => { const data = await searchAPI(searchTerm); diff --git a/docs/framework/solid/reference/functions/createAsyncQueuer.md b/docs/framework/solid/reference/functions/createAsyncQueuer.md index 46733b99..91be6d4f 100644 --- a/docs/framework/solid/reference/functions/createAsyncQueuer.md +++ b/docs/framework/solid/reference/functions/createAsyncQueuer.md @@ -12,7 +12,7 @@ function createAsyncQueuer( selector): SolidAsyncQueuer; ``` -Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:123](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L123) +Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:150](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L150) Creates a Solid-compatible AsyncQueuer instance for managing an asynchronous queue of items, exposing Solid signals for all stateful properties. @@ -38,14 +38,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `activeItems`: Array of items currently being processed @@ -75,7 +85,7 @@ const asyncQueuer = createAsyncQueuer(async (item) => { } }); -// Opt-in to re-render when queue state changes (optimized for UI updates) +// Opt-in to track queue state changes (optimized for UI updates) const asyncQueuer = createAsyncQueuer( async (item) => await fetchData(item), { concurrency: 2, started: true }, @@ -86,7 +96,7 @@ const asyncQueuer = createAsyncQueuer( }) ); -// Opt-in to re-render when processing metrics change (optimized for tracking progress) +// Opt-in to track processing metrics changes (optimized for tracking progress) const asyncQueuer = createAsyncQueuer( async (item) => await fetchData(item), { concurrency: 2, started: true }, diff --git a/docs/framework/solid/reference/functions/createAsyncRateLimiter.md b/docs/framework/solid/reference/functions/createAsyncRateLimiter.md index 616fa58c..f2d4691e 100644 --- a/docs/framework/solid/reference/functions/createAsyncRateLimiter.md +++ b/docs/framework/solid/reference/functions/createAsyncRateLimiter.md @@ -12,7 +12,7 @@ function createAsyncRateLimiter( selector): SolidAsyncRateLimiter; ``` -Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:130](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L130) +Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:202](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L202) A low-level Solid hook that creates an `AsyncRateLimiter` instance to limit how many times an async function can execute within a time window. @@ -50,14 +50,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `currentWindowStart`: Timestamp when the current window started @@ -103,7 +113,7 @@ Available state properties: ```tsx // Default behavior - no reactive state subscriptions -const { maybeExecute } = createAsyncRateLimiter( +const asyncRateLimiter = createAsyncRateLimiter( async (id: string) => { const data = await api.fetchData(id); return data; // Return value is preserved @@ -111,34 +121,79 @@ const { maybeExecute } = createAsyncRateLimiter( { limit: 5, window: 1000 } // 5 calls per second ); -// Opt-in to re-render when rate limit and execution state changes (optimized for UI feedback) -const rateLimiter = createAsyncRateLimiter( - async (query) => { - const result = await searchAPI(query); - return result; +// Subscribe to state changes deep in component tree using Subscribe component + ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + {({ rejectionCount, isExecuting }) => ( +
Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
+ )} +
+ +// Opt-in to track execution state changes at hook level (optimized for loading indicators) +const asyncRateLimiter = createAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; + }, + { limit: 5, window: 1000 }, + (state) => ({ isExecuting: state.isExecuting }) +); + +// Opt-in to track results when available (optimized for data display) +const asyncRateLimiter = createAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; }, - { limit: 10, window: 60000 }, + { limit: 5, window: 1000 }, (state) => ({ - remainingInWindow: state.remainingInWindow, - isExecuting: state.isExecuting, - rejectionCount: state.rejectionCount + lastResult: state.lastResult, + successCount: state.successCount }) ); -// Opt-in to re-render when error state changes (optimized for error handling) -const rateLimiter = createAsyncRateLimiter( - async (query) => { - const result = await searchAPI(query); - return result; +// Opt-in to track error/rejection state changes (optimized for error handling) +const asyncRateLimiter = createAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; }, { - limit: 10, - window: 60000, // 10 calls per minute - onReject: (info) => console.log(`Rate limit exceeded: ${info.nextValidTime - Date.now()}ms until next window`) + limit: 5, + window: 1000, + onError: (error) => console.error('API call failed:', error), + onReject: (rateLimiter) => console.log('Rate limit exceeded') + }, + (state) => ({ + errorCount: state.errorCount, + rejectionCount: state.rejectionCount + }) +); + +// Opt-in to track execution metrics changes (optimized for stats display) +const asyncRateLimiter = createAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; + }, + { limit: 5, window: 1000 }, + (state) => ({ + successCount: state.successCount, + errorCount: state.errorCount, + settleCount: state.settleCount, + rejectionCount: state.rejectionCount + }) +); + +// Opt-in to track execution times changes (optimized for window calculations) +const asyncRateLimiter = createAsyncRateLimiter( + async (id: string) => { + const data = await api.fetchData(id); + return data; }, - (state) => ({ hasError: state.hasError, lastError: state.lastError }) + { limit: 5, window: 1000 }, + (state) => ({ executionTimes: state.executionTimes }) ); // Access the selected state (will be empty object {} unless selector provided) -const { remainingInWindow, isExecuting } = rateLimiter.state(); +const { isExecuting, lastResult, rejectionCount } = asyncRateLimiter.state(); ``` diff --git a/docs/framework/solid/reference/functions/createAsyncThrottler.md b/docs/framework/solid/reference/functions/createAsyncThrottler.md index 18cf29d1..eee0e953 100644 --- a/docs/framework/solid/reference/functions/createAsyncThrottler.md +++ b/docs/framework/solid/reference/functions/createAsyncThrottler.md @@ -12,7 +12,7 @@ function createAsyncThrottler( selector): SolidAsyncThrottler; ``` -Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:118](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L118) +Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:145](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L145) A low-level Solid hook that creates an `AsyncThrottler` instance to limit how often an async function can execute. @@ -36,14 +36,24 @@ Error Handling: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `canLeadingExecute`: Whether the throttler can execute on the leading edge @@ -100,7 +110,7 @@ const { maybeExecute } = createAsyncThrottler( { wait: 1000 } ); -// Opt-in to re-render when isPending or isExecuting changes (optimized for loading states) +// Opt-in to track isPending or isExecuting changes (optimized for loading states) const throttler = createAsyncThrottler( async (query) => { const result = await searchAPI(query); @@ -110,7 +120,7 @@ const throttler = createAsyncThrottler( (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting }) ); -// Opt-in to re-render when error state changes (optimized for error handling) +// Opt-in to track error state changes (optimized for error handling) const throttler = createAsyncThrottler( async (query) => { const result = await searchAPI(query); diff --git a/docs/framework/solid/reference/functions/createBatcher.md b/docs/framework/solid/reference/functions/createBatcher.md index 52a15855..c0f541ee 100644 --- a/docs/framework/solid/reference/functions/createBatcher.md +++ b/docs/framework/solid/reference/functions/createBatcher.md @@ -12,7 +12,7 @@ function createBatcher( selector): SolidBatcher; ``` -Defined in: [solid-pacer/src/batcher/createBatcher.ts:103](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L103) +Defined in: [solid-pacer/src/batcher/createBatcher.ts:130](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L130) Creates a Solid-compatible Batcher instance for managing batches of items, exposing Solid signals for all stateful properties. @@ -30,14 +30,24 @@ The batcher collects items and processes them in batches based on: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `executionCount`: Number of batch executions that have been completed @@ -61,14 +71,14 @@ const batcher = createBatcher( } ); -// Opt-in to re-render when items or isRunning changes (optimized for UI updates) +// Opt-in to track items or isRunning changes (optimized for UI updates) const batcher = createBatcher( (items) => console.log('Processing batch:', items), { maxSize: 5, wait: 2000 }, (state) => ({ items: state.items, isRunning: state.isRunning }) ); -// Opt-in to re-render when execution metrics change (optimized for tracking progress) +// Opt-in to track execution metrics changes (optimized for tracking progress) const batcher = createBatcher( (items) => console.log('Processing batch:', items), { maxSize: 5, wait: 2000 }, diff --git a/docs/framework/solid/reference/functions/createDebouncer.md b/docs/framework/solid/reference/functions/createDebouncer.md index 8102bca7..6ace98c2 100644 --- a/docs/framework/solid/reference/functions/createDebouncer.md +++ b/docs/framework/solid/reference/functions/createDebouncer.md @@ -12,7 +12,7 @@ function createDebouncer( selector): SolidDebouncer; ``` -Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:106](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L106) +Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:133](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L133) A Solid hook that creates and manages a Debouncer instance. @@ -30,14 +30,24 @@ timer resets and starts waiting again. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -84,21 +94,21 @@ const debouncer = createDebouncer( { wait: 500 } ); -// Opt-in to re-render when isPending changes (optimized for loading states) +// Opt-in to track isPending changes (optimized for loading states) const debouncer = createDebouncer( (query: string) => fetchSearchResults(query), { wait: 500 }, (state) => ({ isPending: state.isPending }) ); -// Opt-in to re-render when executionCount changes (optimized for tracking execution) +// Opt-in to track executionCount changes (optimized for tracking execution) const debouncer = createDebouncer( (query: string) => fetchSearchResults(query), { wait: 500 }, (state) => ({ executionCount: state.executionCount }) ); -// Multiple state properties - re-render when any of these change +// Multiple state properties - track when any of these change const debouncer = createDebouncer( (query: string) => fetchSearchResults(query), { wait: 500 }, diff --git a/docs/framework/solid/reference/functions/createQueuedSignal.md b/docs/framework/solid/reference/functions/createQueuedSignal.md index d2cef53e..a93cf4f5 100644 --- a/docs/framework/solid/reference/functions/createQueuedSignal.md +++ b/docs/framework/solid/reference/functions/createQueuedSignal.md @@ -31,13 +31,13 @@ The primitive returns a tuple containing: ## State Management and Selector The primitive uses Solid's reactive state management via the underlying queuer instance. -The `selector` parameter allows you to specify which queuer state changes will trigger a re-render, -optimizing performance by preventing unnecessary re-renders when irrelevant state changes occur. +The `selector` parameter allows you to specify which queuer state changes will trigger reactive updates, +optimizing performance by preventing unnecessary updates when irrelevant state changes occur. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function. This prevents unnecessary updates and gives you +full control over when your component tracks state changes. Only when you provide a selector will the +component track changes to the selected state values. Available queuer state properties: - `executionCount`: Number of items that have been processed by the queuer @@ -95,7 +95,7 @@ const [items, addItem, queue] = createQueuedSignal( } ); -// Opt-in to re-render when queue contents change (optimized for displaying queue items) +// Opt-in to track queue contents changes (optimized for displaying queue items) const [items, addItem, queue] = createQueuedSignal( (item) => console.log('Processing:', item), { started: true, wait: 1000 }, @@ -106,7 +106,7 @@ const [items, addItem, queue] = createQueuedSignal( }) ); -// Opt-in to re-render when processing state changes (optimized for loading indicators) +// Opt-in to track processing state changes (optimized for loading indicators) const [items, addItem, queue] = createQueuedSignal( (item) => console.log('Processing:', item), { started: true, wait: 1000 }, @@ -118,7 +118,7 @@ const [items, addItem, queue] = createQueuedSignal( }) ); -// Opt-in to re-render when execution metrics change (optimized for stats display) +// Opt-in to track execution metrics changes (optimized for stats display) const [items, addItem, queue] = createQueuedSignal( (item) => console.log('Processing:', item), { started: true, wait: 1000 }, diff --git a/docs/framework/solid/reference/functions/createQueuer.md b/docs/framework/solid/reference/functions/createQueuer.md index 8ba68214..cf2ba1c7 100644 --- a/docs/framework/solid/reference/functions/createQueuer.md +++ b/docs/framework/solid/reference/functions/createQueuer.md @@ -12,7 +12,7 @@ function createQueuer( selector): SolidQueuer; ``` -Defined in: [solid-pacer/src/queuer/createQueuer.ts:104](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L104) +Defined in: [solid-pacer/src/queuer/createQueuer.ts:131](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L131) Creates a Solid-compatible Queuer instance for managing a synchronous queue of items, exposing Solid signals for all stateful properties. @@ -32,14 +32,24 @@ By default, the queue uses FIFO behavior, but you can configure LIFO or double-e ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `executionCount`: Number of items that have been processed @@ -62,14 +72,14 @@ const queue = createQueuer( } ); -// Opt-in to re-render when items or isRunning changes (optimized for UI updates) +// Opt-in to track items or isRunning changes (optimized for UI updates) const queue = createQueuer( (item) => console.log('Processing', item), { started: true, wait: 1000 }, (state) => ({ items: state.items, isRunning: state.isRunning }) ); -// Opt-in to re-render when execution metrics change (optimized for tracking progress) +// Opt-in to track execution metrics changes (optimized for tracking progress) const queue = createQueuer( (item) => console.log('Processing', item), { started: true, wait: 1000 }, diff --git a/docs/framework/solid/reference/functions/createRateLimiter.md b/docs/framework/solid/reference/functions/createRateLimiter.md index f138c0f3..e27c3bb8 100644 --- a/docs/framework/solid/reference/functions/createRateLimiter.md +++ b/docs/framework/solid/reference/functions/createRateLimiter.md @@ -12,7 +12,7 @@ function createRateLimiter( selector): SolidRateLimiter; ``` -Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:105](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L105) +Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:173](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L173) A low-level Solid hook that creates a `RateLimiter` instance to enforce rate limits on function execution. @@ -36,14 +36,24 @@ For smoother execution patterns: ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `executionCount`: Number of function executions that have been completed @@ -89,31 +99,72 @@ const rateLimiter = createRateLimiter(apiCall, { limit: 5, window: 60000, windowType: 'sliding', - onReject: (rateLimiter) => { - console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - } }); -// Opt-in to re-render when rate limit state changes (optimized for UI feedback) +// Subscribe to state changes deep in component tree using Subscribe component + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+ +// Opt-in to track execution count changes at hook level (optimized for tracking successful executions) const rateLimiter = createRateLimiter( apiCall, - { limit: 5, window: 60000 }, - (state) => ({ - remainingInWindow: state.remainingInWindow, - rejectionCount: state.rejectionCount - }) + { + limit: 5, + window: 60000, + windowType: 'sliding', + }, + (state) => ({ executionCount: state.executionCount }) +); + +// Opt-in to track rejection count changes (optimized for tracking rate limit violations) +const rateLimiter = createRateLimiter( + apiCall, + { + limit: 5, + window: 60000, + windowType: 'sliding', + }, + (state) => ({ rejectionCount: state.rejectionCount }) +); + +// Opt-in to track execution times changes (optimized for window calculations) +const rateLimiter = createRateLimiter( + apiCall, + { + limit: 5, + window: 60000, + windowType: 'sliding', + }, + (state) => ({ executionTimes: state.executionTimes }) ); -// Opt-in to re-render when execution metrics change (optimized for tracking progress) +// Multiple state properties - track when any of these change const rateLimiter = createRateLimiter( apiCall, - { limit: 5, window: 60000 }, + { + limit: 5, + window: 60000, + windowType: 'sliding', + }, (state) => ({ executionCount: state.executionCount, - nextWindowTime: state.nextWindowTime + rejectionCount: state.rejectionCount }) ); +// Monitor rate limit status +const handleClick = () => { + const remaining = rateLimiter.getRemainingInWindow(); + if (remaining > 0) { + rateLimiter.maybeExecute(data); + } else { + showRateLimitWarning(); + } +}; + // Access the selected state (will be empty object {} unless selector provided) -const { remainingInWindow, rejectionCount } = rateLimiter.state(); +const { executionCount, rejectionCount } = rateLimiter.state(); ``` diff --git a/docs/framework/solid/reference/functions/createThrottledSignal.md b/docs/framework/solid/reference/functions/createThrottledSignal.md index 8d05f569..7f9d6bd2 100644 --- a/docs/framework/solid/reference/functions/createThrottledSignal.md +++ b/docs/framework/solid/reference/functions/createThrottledSignal.md @@ -18,7 +18,7 @@ A Solid hook that creates a throttled state value that updates at most once with This hook combines Solid's createSignal with throttling functionality to provide controlled state updates. Throttling ensures state updates occur at a controlled rate regardless of how frequently the setter is called. -This is useful for rate-limiting expensive re-renders or operations that depend on rapidly changing state. +This is useful for rate-limiting expensive updates or operations that depend on rapidly changing state. The hook returns a tuple containing: - The throttled state value accessor diff --git a/docs/framework/solid/reference/functions/createThrottledValue.md b/docs/framework/solid/reference/functions/createThrottledValue.md index 7522aafc..c7d69431 100644 --- a/docs/framework/solid/reference/functions/createThrottledValue.md +++ b/docs/framework/solid/reference/functions/createThrottledValue.md @@ -18,7 +18,7 @@ A high-level Solid hook that creates a throttled version of a value that updates This hook uses Solid's createSignal internally to manage the throttled state. Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes. -This is useful for rate-limiting expensive re-renders or API calls that depend on rapidly changing values. +This is useful for rate-limiting expensive updates or API calls that depend on rapidly changing values. The hook returns a tuple containing: - An accessor function that provides the throttled value diff --git a/docs/framework/solid/reference/functions/createThrottler.md b/docs/framework/solid/reference/functions/createThrottler.md index d7e5a901..88eb6377 100644 --- a/docs/framework/solid/reference/functions/createThrottler.md +++ b/docs/framework/solid/reference/functions/createThrottler.md @@ -12,7 +12,7 @@ function createThrottler( selector): SolidThrottler; ``` -Defined in: [solid-pacer/src/throttler/createThrottler.ts:102](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L102) +Defined in: [solid-pacer/src/throttler/createThrottler.ts:136](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L136) A low-level Solid hook that creates a `Throttler` instance that limits how often the provided function can execute. @@ -26,14 +26,24 @@ expensive operations or UI updates. ## State Management and Selector -The hook uses TanStack Store for reactive state management. The `selector` parameter allows you -to specify which state changes will trigger a re-render, optimizing performance by preventing -unnecessary re-renders when irrelevant state changes occur. +The hook uses TanStack Store for reactive state management. You can subscribe to state changes +in two ways: + +**1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, there will be no reactive state subscriptions** and you must opt-in to state -tracking by providing a selector function. This prevents unnecessary re-renders and gives you -full control over when your component updates. Only when you provide a selector will the -component re-render when the selected state values change. +tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary +updates and gives you full control over when your component tracks state changes. Available state properties: - `canLeadingExecute`: Whether the throttler can execute on the leading edge @@ -80,21 +90,28 @@ Available state properties: // Default behavior - no reactive state subscriptions const throttler = createThrottler(setValue, { wait: 1000 }); -// Opt-in to re-render when isPending changes (optimized for loading states) +// Subscribe to state changes deep in component tree using Subscribe component + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+ +// Opt-in to track isPending changes at hook level (optimized for loading states) const throttler = createThrottler( setValue, { wait: 1000 }, (state) => ({ isPending: state.isPending }) ); -// Opt-in to re-render when executionCount changes (optimized for tracking execution) +// Opt-in to track executionCount changes (optimized for tracking execution) const throttler = createThrottler( setValue, { wait: 1000 }, (state) => ({ executionCount: state.executionCount }) ); -// Multiple state properties - re-render when any of these change +// Multiple state properties - track when any of these change const throttler = createThrottler( setValue, { diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md b/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md index bea10538..89dcb056 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:11](https://git readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L20) +Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:37](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L37) Reactive state that will be updated when the batcher state changes @@ -44,10 +44,57 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L26) +Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:43](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L43) #### Deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/async-batcher/createAsyncBatcher.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts#L28) + +A Solid component that allows you to subscribe to the batcher state. + +This is useful for tracking specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ size: state.size, isExecuting: state.isExecuting })}> + {({ size, isExecuting }) => ( +
Batch: {size} items, {isExecuting ? 'Executing...' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md b/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md index 3614e257..152b505f 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:13](https:/ readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L22) +Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L39) Reactive state that will be updated when the debouncer state changes @@ -44,10 +44,57 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L28) +Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L45) #### Deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/async-debouncer/createAsyncDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts#L30) + +A Solid component that allows you to subscribe to the debouncer state. + +This is useful for tracking specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ isPending: state.isPending, isExecuting: state.isExecuting })}> + {({ isPending, isExecuting }) => ( +
{isPending ? 'Waiting...' : isExecuting ? 'Executing...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md b/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md index b86fc775..0729759b 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:11](https://githu readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:20](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L20) +Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:37](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L37) Reactive state that will be updated when the queuer state changes @@ -44,10 +44,57 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:26](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L26) +Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:43](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L43) #### Deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/async-queuer/createAsyncQueuer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts#L28) + +A Solid component that allows you to subscribe to the queuer state. + +This is useful for tracking specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ pendingItems: state.pendingItems, activeItems: state.activeItems })}> + {({ pendingItems, activeItems }) => ( +
Pending: {pendingItems.length}, Active: {activeItems.length}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md b/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md index 85111b2b..c26dae9e 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:12](ht readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L21) +Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:38](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L38) Reactive state that will be updated when the rate limiter state changes @@ -44,10 +44,57 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L27) +Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:44](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L44) #### Deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts:29](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts#L29) + +A Solid component that allows you to subscribe to the rate limiter state. + +This is useful for tracking specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + {({ rejectionCount, isExecuting }) => ( +
Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md b/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md index 5fe34f91..88f130b6 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:12](https:/ readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L21) +Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:38](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L38) Reactive state that will be updated when the throttler state changes @@ -44,10 +44,57 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L27) +Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:44](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L44) #### Deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/async-throttler/createAsyncThrottler.ts:29](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts#L29) + +A Solid component that allows you to subscribe to the throttler state. + +This is useful for tracking specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ isPending: state.isPending, isExecuting: state.isExecuting })}> + {({ isPending, isExecuting }) => ( +
{isPending ? 'Pending...' : isExecuting ? 'Executing...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidBatcher.md b/docs/framework/solid/reference/interfaces/SolidBatcher.md index 955c83fd..6e2f94a0 100644 --- a/docs/framework/solid/reference/interfaces/SolidBatcher.md +++ b/docs/framework/solid/reference/interfaces/SolidBatcher.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/batcher/createBatcher.ts:8](https://github.com/TanS readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/batcher/createBatcher.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L17) +Defined in: [solid-pacer/src/batcher/createBatcher.ts:34](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L34) Reactive state that will be updated when the batcher state changes @@ -44,10 +44,57 @@ Use this instead of `batcher.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/batcher/createBatcher.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L23) +Defined in: [solid-pacer/src/batcher/createBatcher.ts:40](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L40) #### Deprecated Use `batcher.state` instead of `batcher.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/batcher/createBatcher.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/batcher/createBatcher.ts#L25) + +A Solid component that allows you to subscribe to the batcher state. + +This is useful for tracking specific parts of the batcher state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Batch: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidDebouncer.md b/docs/framework/solid/reference/interfaces/SolidDebouncer.md index e7468525..0302de26 100644 --- a/docs/framework/solid/reference/interfaces/SolidDebouncer.md +++ b/docs/framework/solid/reference/interfaces/SolidDebouncer.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:13](https://github.com readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L22) +Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L39) Reactive state that will be updated when the debouncer state changes @@ -44,10 +44,57 @@ Use this instead of `debouncer.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L28) +Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:45](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L45) #### Deprecated Use `debouncer.state` instead of `debouncer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/debouncer/createDebouncer.ts:30](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/debouncer/createDebouncer.ts#L30) + +A Solid component that allows you to subscribe to the debouncer state. + +This is useful for tracking specific parts of the debouncer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Waiting...' : 'Ready'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidQueuer.md b/docs/framework/solid/reference/interfaces/SolidQueuer.md index 598fc8ea..0041cdf0 100644 --- a/docs/framework/solid/reference/interfaces/SolidQueuer.md +++ b/docs/framework/solid/reference/interfaces/SolidQueuer.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/queuer/createQueuer.ts:8](https://github.com/TanSta readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/queuer/createQueuer.ts:17](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L17) +Defined in: [solid-pacer/src/queuer/createQueuer.ts:34](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L34) Reactive state that will be updated when the queuer state changes @@ -44,10 +44,57 @@ Use this instead of `queuer.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/queuer/createQueuer.ts:23](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L23) +Defined in: [solid-pacer/src/queuer/createQueuer.ts:40](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L40) #### Deprecated Use `queuer.state` instead of `queuer.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/queuer/createQueuer.ts:25](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/queuer/createQueuer.ts#L25) + +A Solid component that allows you to subscribe to the queuer state. + +This is useful for tracking specific parts of the queuer state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ size: state.size, isRunning: state.isRunning })}> + {({ size, isRunning }) => ( +
Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidRateLimiter.md b/docs/framework/solid/reference/interfaces/SolidRateLimiter.md index 622e6ac4..8f732e78 100644 --- a/docs/framework/solid/reference/interfaces/SolidRateLimiter.md +++ b/docs/framework/solid/reference/interfaces/SolidRateLimiter.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:12](https://githu readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:21](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L21) +Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:38](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L38) Reactive state that will be updated when the rate limiter state changes @@ -44,10 +44,57 @@ Use this instead of `rateLimiter.store.state` readonly store: Store>; ``` -Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:27](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L27) +Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:44](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L44) #### Deprecated Use `rateLimiter.state` instead of `rateLimiter.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/rate-limiter/createRateLimiter.ts:29](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts#L29) + +A Solid component that allows you to subscribe to the rate limiter state. + +This is useful for tracking specific parts of the rate limiter state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ rejectionCount: state.rejectionCount })}> + {({ rejectionCount }) => ( +
Rejections: {rejectionCount}
+ )} +
+``` diff --git a/docs/framework/solid/reference/interfaces/SolidThrottler.md b/docs/framework/solid/reference/interfaces/SolidThrottler.md index ace7d889..f75e7c65 100644 --- a/docs/framework/solid/reference/interfaces/SolidThrottler.md +++ b/docs/framework/solid/reference/interfaces/SolidThrottler.md @@ -30,7 +30,7 @@ Defined in: [solid-pacer/src/throttler/createThrottler.ts:13](https://github.com readonly state: Accessor>; ``` -Defined in: [solid-pacer/src/throttler/createThrottler.ts:22](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L22) +Defined in: [solid-pacer/src/throttler/createThrottler.ts:39](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L39) Reactive state that will be updated when the throttler state changes @@ -44,10 +44,57 @@ Use this instead of `throttler.store.state` readonly store: Store>>; ``` -Defined in: [solid-pacer/src/throttler/createThrottler.ts:28](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L28) +Defined in: [solid-pacer/src/throttler/createThrottler.ts:45](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L45) #### Deprecated Use `throttler.state` instead of `throttler.store.state` if you want to read reactive state. The state on the store object is not reactive, as it has not been wrapped in a `useStore` hook internally. Although, you can make the state reactive by using the `useStore` in your own usage. + +*** + +### Subscribe() + +```ts +Subscribe: (props) => Element; +``` + +Defined in: [solid-pacer/src/throttler/createThrottler.ts:30](https://github.com/TanStack/pacer/blob/main/packages/solid-pacer/src/throttler/createThrottler.ts#L30) + +A Solid component that allows you to subscribe to the throttler state. + +This is useful for tracking specific parts of the throttler state +deep in your component tree without needing to pass a selector to the hook. + +#### Type Parameters + +##### TSelected + +`TSelected` + +#### Parameters + +##### props + +###### children + +`Element` \| (`state`) => `Element` + +###### selector + +(`state`) => `TSelected` + +#### Returns + +`Element` + +#### Example + +```ts + ({ isPending: state.isPending })}> + {({ isPending }) => ( +
{isPending ? 'Loading...' : 'Ready'}
+ )} +
+``` diff --git a/docs/guides/async-batching.md b/docs/guides/async-batching.md index cfc41577..e3da568a 100644 --- a/docs/guides/async-batching.md +++ b/docs/guides/async-batching.md @@ -347,7 +347,27 @@ The `AsyncBatcher` class uses TanStack Store for reactive state management, prov ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `asyncBatcher.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const asyncBatcher = useAsyncBatcher(asyncBatchFn, { maxSize: 5, wait: 1000 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isExecuting: state.isExecuting })}> + {(state) => ( +
{state.isExecuting ? 'Executing...' : 'Idle'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `asyncBatcher.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/async-debouncing.md b/docs/guides/async-debouncing.md index 32c1b2e3..e97dbd71 100644 --- a/docs/guides/async-debouncing.md +++ b/docs/guides/async-debouncing.md @@ -206,9 +206,29 @@ The `AsyncDebouncer` class uses TanStack Store for reactive state management, pr ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: -**By default, `debouncer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. +**1. Using `asyncDebouncer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const asyncDebouncer = useAsyncDebouncer(asyncFn, { wait: 500 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isExecuting: state.isExecuting })}> + {(state) => ( +
{state.isExecuting ? 'Executing...' : 'Idle'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. + +**By default, `asyncDebouncer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. ```ts // Default behavior - no reactive state subscriptions diff --git a/docs/guides/async-queuing.md b/docs/guides/async-queuing.md index 79b24836..f98c1edc 100644 --- a/docs/guides/async-queuing.md +++ b/docs/guides/async-queuing.md @@ -339,7 +339,27 @@ The `AsyncQueuer` class uses TanStack Store for reactive state management, provi ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `asyncQueuer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const asyncQueuer = useAsyncQueuer(processFn, { concurrency: 2, wait: 1000 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ activeItems: state.activeItems })}> + {(state) => ( +
Active items: {state.activeItems.length}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `asyncQueuer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/async-rate-limiting.md b/docs/guides/async-rate-limiting.md index c9dd2192..ad3cb0de 100644 --- a/docs/guides/async-rate-limiting.md +++ b/docs/guides/async-rate-limiting.md @@ -197,9 +197,29 @@ The `AsyncRateLimiter` class uses TanStack Store for reactive state management, ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: -**By default, `rateLimiter.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. +**1. Using `asyncRateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const asyncRateLimiter = useAsyncRateLimiter(asyncFn, { limit: 5, window: 1000 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isExecuting: state.isExecuting })}> + {(state) => ( +
{state.isExecuting ? 'Executing...' : 'Idle'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. + +**By default, `asyncRateLimiter.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. ```ts // Default behavior - no reactive state subscriptions diff --git a/docs/guides/async-throttling.md b/docs/guides/async-throttling.md index f3523435..981f9ad3 100644 --- a/docs/guides/async-throttling.md +++ b/docs/guides/async-throttling.md @@ -211,9 +211,29 @@ The `AsyncThrottler` class uses TanStack Store for reactive state management, pr ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: -**By default, `throttler.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. +**1. Using `asyncThrottler.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const asyncThrottler = useAsyncThrottler(asyncFn, { wait: 500 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isExecuting: state.isExecuting })}> + {(state) => ( +
{state.isExecuting ? 'Executing...' : 'Idle'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. + +**By default, `asyncThrottler.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. ```ts // Default behavior - no reactive state subscriptions diff --git a/docs/guides/batching.md b/docs/guides/batching.md index ce55b9f8..65359cbe 100644 --- a/docs/guides/batching.md +++ b/docs/guides/batching.md @@ -186,7 +186,27 @@ The `Batcher` class uses TanStack Store for reactive state management, providing ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const batcher = useBatcher(processFn, { maxSize: 5, wait: 1000 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ size: state.size })}> + {(state) => ( +
Batch size: {state.size}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `batcher.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/debouncing.md b/docs/guides/debouncing.md index 7a297667..d6c8482a 100644 --- a/docs/guides/debouncing.md +++ b/docs/guides/debouncing.md @@ -220,7 +220,27 @@ The `Debouncer` class uses TanStack Store for reactive state management, providi ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const debouncer = useDebouncer(fn, { wait: 500 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isPending: state.isPending })}> + {(state) => ( +
{state.isPending ? 'Waiting...' : 'Ready'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `debouncer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/queuing.md b/docs/guides/queuing.md index 92d1e684..3c3117a9 100644 --- a/docs/guides/queuing.md +++ b/docs/guides/queuing.md @@ -423,7 +423,27 @@ The `Queuer` class uses TanStack Store for reactive state management, providing ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const queuer = useQueuer(processFn, { wait: 1000, maxSize: 10 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ size: state.size })}> + {(state) => ( +
Queue size: {state.size}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `queuer.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/rate-limiting.md b/docs/guides/rate-limiting.md index 18e85bc5..cd723773 100644 --- a/docs/guides/rate-limiting.md +++ b/docs/guides/rate-limiting.md @@ -239,7 +239,27 @@ The `RateLimiter` class uses TanStack Store for reactive state management, provi ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const rateLimiter = useRateLimiter(fn, { limit: 5, window: 1000 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ rejectionCount: state.rejectionCount })}> + {(state) => ( +
Rejections: {state.rejectionCount}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `rateLimiter.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/docs/guides/throttling.md b/docs/guides/throttling.md index 9e782bed..ce405188 100644 --- a/docs/guides/throttling.md +++ b/docs/guides/throttling.md @@ -211,7 +211,27 @@ The `Throttler` class uses TanStack Store for reactive state management, providi ### State Selector (Framework Adapters) -Framework adapters support a `selector` argument that allows you to specify which state changes will trigger re-renders. This optimizes performance by preventing unnecessary re-renders when irrelevant state changes occur. +Framework adapters support subscribing to state changes in two ways: + +**1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)** + +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. + +```tsx +// Default behavior - no reactive state subscriptions at hook level +const throttler = useThrottler(fn, { wait: 200 }) + +// Subscribe to state changes deep in component tree using Subscribe component + ({ isPending: state.isPending })}> + {(state) => ( +
{state.isPending ? 'Waiting...' : 'Ready'}
+ )} +
+``` + +**2. Using the `selector` parameter (For hook-level subscriptions)** + +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. **By default, `throttler.state` is empty (`{}`) as the selector is empty by default.** This is where reactive state from a TanStack Store `useStore` gets stored. You must opt-in to state tracking by providing a selector function. diff --git a/examples/preact/useAsyncBatcher/src/index.tsx b/examples/preact/useAsyncBatcher/src/index.tsx index 6852618c..f40d032f 100644 --- a/examples/preact/useAsyncBatcher/src/index.tsx +++ b/examples/preact/useAsyncBatcher/src/index.tsx @@ -39,47 +39,35 @@ function App() { return result } - const asyncBatcher = useAsyncBatcher( - processBatch, - { - maxSize: 5, // Process in batches of 5 (if reached before wait time) - wait: 4000, // Wait up to 4 seconds before processing a batch - getShouldExecute: (items) => - items.some((item) => item.value.includes('urgent')), // Process immediately if any item is marked urgent - throwOnError: false, // Don't throw errors, handle them via onError - onSuccess: (result, batch, batcher) => { - console.log('Batch succeeded:', result) - console.log('Processed batch:', batch) - console.log( - 'Total successful batches:', - batcher.store.state.successCount, - ) - }, - onError: (error: any, _batcher) => { - console.error('Batch failed:', error) - setErrors((prev) => [ - ...prev, - `Error: ${error} (${new Date().toLocaleTimeString()})`, - ]) - }, - onSettled: (batch, batcher) => { - console.log('Batch settled:', batch) - console.log( - 'Total processed items:', - batcher.store.state.totalItemsProcessed, - ) - }, + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const asyncBatcher = useAsyncBatcher(processBatch, { + maxSize: 5, // Process in batches of 5 (if reached before wait time) + wait: 4000, // Wait up to 4 seconds before processing a batch + getShouldExecute: (items) => + items.some((item) => item.value.includes('urgent')), // Process immediately if any item is marked urgent + throwOnError: false, // Don't throw errors, handle them via onError + onSuccess: (result, batch, batcher) => { + console.log('Batch succeeded:', result) + console.log('Processed batch:', batch) + console.log('Total successful batches:', batcher.store.state.successCount) }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isExecuting: state.isExecuting, - status: state.status, - successCount: state.successCount, - errorCount: state.errorCount, - totalItemsProcessed: state.totalItemsProcessed, - }), - ) + onError: (error: any, _batcher) => { + console.error('Batch failed:', error) + setErrors((prev) => [ + ...prev, + `Error: ${error} (${new Date().toLocaleTimeString()})`, + ]) + }, + onSettled: (batch, batcher) => { + console.log('Batch settled:', batch) + console.log( + 'Total processed items:', + batcher.store.state.totalItemsProcessed, + ) + }, + }) + // Alternative to asyncBatcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, const addItem = (isUrgent = false) => { const nextId = Date.now() @@ -104,67 +92,83 @@ function App() {

TanStack Pacer useAsyncBatcher Example

-
-

Batch Status

-
Current Batch Size: {asyncBatcher.state.size}
-
Max Batch Size: 5
-
Is Executing: {asyncBatcher.state.isExecuting ? 'Yes' : 'No'}
-
Status: {asyncBatcher.state.status}
-
Successful Batches: {asyncBatcher.state.successCount}
-
Failed Batches: {asyncBatcher.state.errorCount}
-
- Total Items Processed: {asyncBatcher.state.totalItemsProcessed} -
-
- -
-

Current Batch Items

-
- {asyncBatcher.peekAllItems().length === 0 ? ( - No items in current batch - ) : ( - asyncBatcher.peekAllItems().map((item, index) => ( -
- {index + 1}: {item.value} (added at{' '} - {new Date(item.timestamp).toLocaleTimeString()}) + ({ + size: state.size, + isExecuting: state.isExecuting, + status: state.status, + successCount: state.successCount, + errorCount: state.errorCount, + totalItemsProcessed: state.totalItemsProcessed, + })} + > + {({ + size, + isExecuting, + status, + successCount, + errorCount, + totalItemsProcessed, + }) => ( + <> +
+

Batch Status

+
Current Batch Size: {size}
+
Max Batch Size: 5
+
Is Executing: {isExecuting ? 'Yes' : 'No'}
+
Status: {status}
+
Successful Batches: {successCount}
+
Failed Batches: {errorCount}
+
Total Items Processed: {totalItemsProcessed}
+
+ +
+

Current Batch Items

+
+ {asyncBatcher.peekAllItems().length === 0 ? ( + No items in current batch + ) : ( + asyncBatcher.peekAllItems().map((item, index) => ( +
+ {index + 1}: {item.value} (added at{' '} + {new Date(item.timestamp).toLocaleTimeString()}) +
+ )) + )}
- )) - )} -
-
- -
-

Controls

-
- - - - -
-
+
+ +
+

Controls

+
+ + + + +
+
+ + )} +

Processed Batches ({processedBatches.length})

@@ -195,9 +199,13 @@ function App() {
)} -
-        {JSON.stringify(asyncBatcher.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncDebouncer/src/index.tsx b/examples/preact/useAsyncDebouncer/src/index.tsx index c0861672..1c7154bf 100644 --- a/examples/preact/useAsyncDebouncer/src/index.tsx +++ b/examples/preact/useAsyncDebouncer/src/index.tsx @@ -38,29 +38,23 @@ function App() { } // hook that gives you an async debouncer instance - const asyncDebouncer = useAsyncDebouncer( - handleSearch, - { - // leading: true, // optional leading execution - wait: 500, // Wait 500ms between API calls - onError: (error) => { - // optional error handler - console.error('Search failed:', error) - setResults([]) - }, - // throwOnError: true, - asyncRetryerOptions: { - maxAttempts: 3, - maxExecutionTime: 1000, - }, + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const asyncDebouncer = useAsyncDebouncer(handleSearch, { + // leading: true, // optional leading execution + wait: 500, // Wait 500ms between API calls + onError: (error) => { + // optional error handler + console.error('Search failed:', error) + setResults([]) }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isExecuting: state.isExecuting, - isPending: state.isPending, - successCount: state.successCount, - }), - ) + // throwOnError: true, + asyncRetryerOptions: { + maxAttempts: 3, + maxExecutionTime: 1000, + }, + }) + // Alternative to asyncDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, // get and name our debounced function const handleSearchDebounced = asyncDebouncer.maybeExecute @@ -90,21 +84,35 @@ function App() {
-
-

API calls made: {asyncDebouncer.state.successCount}

- {results.length > 0 && ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
+ ({ + isExecuting: state.isExecuting, + isPending: state.isPending, + successCount: state.successCount, + })} + > + {({ isExecuting, isPending, successCount }) => ( +
+

API calls made: {successCount}

+ {results.length > 0 && ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ )} + {isPending &&

Pending...

} + {isExecuting &&

Executing...

} +
)} - {asyncDebouncer.state.isPending &&

Pending...

} - {asyncDebouncer.state.isExecuting &&

Executing...

} -
-          {JSON.stringify(asyncDebouncer.store.state, null, 2)}
-        
-
+ + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncQueuedState/src/index.tsx b/examples/preact/useAsyncQueuedState/src/index.tsx index b243fcd3..36217f99 100644 --- a/examples/preact/useAsyncQueuedState/src/index.tsx +++ b/examples/preact/useAsyncQueuedState/src/index.tsx @@ -16,122 +16,136 @@ function App() { console.log(`Processed ${item}`) } - const [queueItems, asyncQueuer] = useAsyncQueuedState( - processItem, // your function to queue/process items - { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, i) => i + 1), - concurrency, // Process 2 items concurrently - started: false, - wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onReject: (item: Item, asyncQueuer) => { - console.log( - 'Queue is full, rejecting item', - item, - asyncQueuer.store.state.rejectionCount, - ) - }, - onError: (error, item: Item, asyncQueuer) => { - console.error( - `Error processing item: ${item}`, - error, - asyncQueuer.store.state.errorCount, - ) // optionally, handle errors here instead of your own try/catch - }, + // Note: useAsyncQueuedState requires items in selector, but we'll use Subscribe for reactive rendering + const [queueItems, asyncQueuer] = useAsyncQueuedState(processItem, { + // your function to queue/process items + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, i) => i + 1), + concurrency, // Process 2 items concurrently + started: false, + wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency + onReject: (item: Item, asyncQueuer) => { + console.log( + 'Queue is full, rejecting item', + item, + asyncQueuer.store.state.rejectionCount, + ) }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - activeItems: state.activeItems, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - isRunning: state.isRunning, - items: state.items, // required for useAsyncQueuedState hook - rejectionCount: state.rejectionCount, - size: state.size, - status: state.status, - successCount: state.successCount, - }), - ) + onError: (error, item: Item, asyncQueuer) => { + console.error( + `Error processing item: ${item}`, + error, + asyncQueuer.store.state.errorCount, + ) // optionally, handle errors here instead of your own try/catch + }, + // Alternative to asyncQueuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) return (

TanStack Pacer useAsyncQueuer Example

-
-
Queue Size: {asyncQueuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {asyncQueuer.state.status}
-
Items Processed: {asyncQueuer.state.successCount}
-
Items Rejected: {asyncQueuer.state.rejectionCount}
-
Active Tasks: {asyncQueuer.peekActiveItems().length}
-
Pending Tasks: {asyncQueuer.peekPendingItems().length}
-
- Concurrency:{' '} - - setConcurrency(Math.max(1, parseInt(e.currentTarget.value) || 1)) - } - style={{ width: '60px' }} - /> -
-
- Queue Items: - {queueItems.map((item, index) => ( -
- {index}: {item} -
- ))} -
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + successCount: state.successCount, + rejectionCount: state.rejectionCount, + items: state.items, + isRunning: state.isRunning, + })} > - - - -
- - -
-
-        {JSON.stringify(asyncQueuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + successCount, + rejectionCount, + isRunning, + }) => ( + <> +
+
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {successCount}
+
Items Rejected: {rejectionCount}
+
Active Tasks: {asyncQueuer.peekActiveItems().length}
+
Pending Tasks: {asyncQueuer.peekPendingItems().length}
+
+ Concurrency:{' '} + + setConcurrency( + Math.max(1, parseInt(e.currentTarget.value) || 1), + ) + } + style={{ width: '60px' }} + /> +
+
+ Queue Items: + {queueItems.map((item, index) => ( +
+ {index}: {item} +
+ ))} +
+
+ + + +
+ + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncQueuer/src/index.tsx b/examples/preact/useAsyncQueuer/src/index.tsx index 0a36ee74..c64897ba 100644 --- a/examples/preact/useAsyncQueuer/src/index.tsx +++ b/examples/preact/useAsyncQueuer/src/index.tsx @@ -16,128 +16,141 @@ function App() { console.log(`Processed ${item}`) } - const asyncQueuer = useAsyncQueuer( - processItem, // your function to queue/process items - { - maxSize: 25, - initialItems: Array.from({ length: 10 }, (_, i) => i + 1), - concurrency, // Process 2 items concurrently - started: false, - wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency - onReject: (item, asyncQueuer) => { - console.log( - 'Queue is full, rejecting item', - item, - asyncQueuer.store.state.rejectionCount, - ) - }, - onError: (error, item: Item, asyncQueuer) => { - console.error( - `Error processing item: ${item}`, - error, - asyncQueuer.store.state.errorCount, - ) // optionally, handle errors here instead of your own try/catch - }, + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const asyncQueuer = useAsyncQueuer(processItem, { + // your function to queue/process items + maxSize: 25, + initialItems: Array.from({ length: 10 }, (_, i) => i + 1), + concurrency, // Process 2 items concurrently + started: false, + wait: 100, // for demo purposes - usually you would not want extra wait time if you are also throttling with concurrency + onReject: (item, asyncQueuer) => { + console.log( + 'Queue is full, rejecting item', + item, + asyncQueuer.store.state.rejectionCount, + ) }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - successCount: state.successCount, - rejectionCount: state.rejectionCount, - activeItems: state.activeItems, - items: state.items, - isRunning: state.isRunning, - }), - ) + onError: (error, item: Item, asyncQueuer) => { + console.error( + `Error processing item: ${item}`, + error, + asyncQueuer.store.state.errorCount, + ) // optionally, handle errors here instead of your own try/catch + }, + }) + // Alternative to asyncQueuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, return (

TanStack Pacer useAsyncQueuer Example

-
-
Queue Size: {asyncQueuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {asyncQueuer.state.status}
-
Items Processed: {asyncQueuer.state.successCount}
-
Items Rejected: {asyncQueuer.state.rejectionCount}
-
Active Tasks: {asyncQueuer.state.activeItems.length}
-
Pending Tasks: {asyncQueuer.state.items.length}
-
- Concurrency:{' '} - - setConcurrency(Math.max(1, parseInt(e.currentTarget.value) || 1)) - } - style={{ width: '60px' }} - /> -
-
- Queue Items: - {asyncQueuer.peekAllItems().map((item, index) => ( -
- {index}: {item} -
- ))} -
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + successCount: state.successCount, + rejectionCount: state.rejectionCount, + activeItems: state.activeItems, + items: state.items, + isRunning: state.isRunning, + })} > - - - - - - - -
-
-        {JSON.stringify(asyncQueuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + successCount, + rejectionCount, + activeItems, + items, + isRunning, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {successCount}
+
Items Rejected: {rejectionCount}
+
Active Tasks: {activeItems.length}
+
Pending Tasks: {items.length}
+
+ Concurrency:{' '} + + setConcurrency( + Math.max(1, parseInt(e.currentTarget.value) || 1), + ) + } + style={{ width: '60px' }} + /> +
+
+ Queue Items: + {asyncQueuer.peekAllItems().map((item, index) => ( +
+ {index}: {item} +
+ ))} +
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncRateLimiter/src/index.tsx b/examples/preact/useAsyncRateLimiter/src/index.tsx index 20e7b784..1e8eccac 100644 --- a/examples/preact/useAsyncRateLimiter/src/index.tsx +++ b/examples/preact/useAsyncRateLimiter/src/index.tsx @@ -37,36 +37,28 @@ function App() { const data = await fakeApi(term) setResults(data) setError(null) - - console.log(setSearchAsyncRateLimiter.state.successCount) } // hook that gives you an async rate limiter instance - const setSearchAsyncRateLimiter = useAsyncRateLimiter( - handleSearch, - { - windowType: windowType, - limit: 3, // Maximum 2 requests - window: 3000, // per 1 second - onReject: (_args, rateLimiter) => { - console.log( - `Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`, - ) - }, - onError: (error) => { - // optional error handler - console.error('Search failed:', error) - setError(error as Error) - setResults([]) - }, + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setSearchAsyncRateLimiter = useAsyncRateLimiter(handleSearch, { + windowType: windowType, + limit: 3, // Maximum 2 requests + window: 3000, // per 1 second + onReject: (_args, rateLimiter) => { + console.log( + `Rate limit reached. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`, + ) }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - successCount: state.successCount, - rejectionCount: state.rejectionCount, - isExecuting: state.isExecuting, - }), - ) + onError: (error) => { + // optional error handler + console.error('Search failed:', error) + setError(error as Error) + setResults([]) + }, + }) + // Alternative to setSearchAsyncRateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, // get and name our rate limited function const handleSearchRateLimited = setSearchAsyncRateLimiter.maybeExecute @@ -123,43 +115,55 @@ function App() { /> {error &&
Error: {error.message}
} -
- - - - - - - - - - - - - - - - - - - -
API calls made:{setSearchAsyncRateLimiter.state.successCount}
Rejected calls:{setSearchAsyncRateLimiter.state.rejectionCount}
Is executing: - {setSearchAsyncRateLimiter.state.isExecuting ? 'Yes' : 'No'} -
Results: - {results.length > 0 ? ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
- ) : ( - 'No results' - )} -
-
-
-        {JSON.stringify(setSearchAsyncRateLimiter.store.state, null, 2)}
-      
+ ({ + successCount: state.successCount, + rejectionCount: state.rejectionCount, + isExecuting: state.isExecuting, + })} + > + {({ successCount, rejectionCount, isExecuting }) => ( +
+ + + + + + + + + + + + + + + + + + + +
API calls made:{successCount}
Rejected calls:{rejectionCount}
Is executing:{isExecuting ? 'Yes' : 'No'}
Results: + {results.length > 0 ? ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ ) : ( + 'No results' + )} +
+
+ )} +
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncRateLimiterWithPersister/src/index.tsx b/examples/preact/useAsyncRateLimiterWithPersister/src/index.tsx index c7a54b7a..b78562d5 100644 --- a/examples/preact/useAsyncRateLimiterWithPersister/src/index.tsx +++ b/examples/preact/useAsyncRateLimiterWithPersister/src/index.tsx @@ -38,8 +38,6 @@ function App() { const data = await fakeApi(term) setResults(data) setError(null) - - console.log(setSearchAsyncRateLimiter.state.successCount) } // const rateLimiterPersister = useStoragePersister< @@ -72,13 +70,13 @@ function App() { // optionally, you can persist the rate limiter state to localStorage // initialState: rateLimiterPersister.loadState(), }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to setSearchAsyncRateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // useEffect(() => { - // rateLimiterPersister.saveState(setSearchAsyncRateLimiter.state) - // }, [setSearchAsyncRateLimiter.state]) + // rateLimiterPersister.saveState(setSearchAsyncRateLimiter.store.state) + // }, [setSearchAsyncRateLimiter.store.state]) // get and name our rate limited function const handleSearchRateLimited = setSearchAsyncRateLimiter.maybeExecute @@ -135,43 +133,55 @@ function App() { /> {error &&
Error: {error.message}
} -
- - - - - - - - - - - - - - - - - - - -
API calls made:{setSearchAsyncRateLimiter.state.successCount}
Rejected calls:{setSearchAsyncRateLimiter.state.rejectionCount}
Is executing: - {setSearchAsyncRateLimiter.state.isExecuting ? 'Yes' : 'No'} -
Results: - {results.length > 0 ? ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
- ) : ( - 'No results' - )} -
-
-
-        {JSON.stringify(setSearchAsyncRateLimiter.store.state, null, 2)}
-      
+ ({ + successCount: state.successCount, + rejectionCount: state.rejectionCount, + isExecuting: state.isExecuting, + })} + > + {({ successCount, rejectionCount, isExecuting }) => ( +
+ + + + + + + + + + + + + + + + + + + +
API calls made:{successCount}
Rejected calls:{rejectionCount}
Is executing:{isExecuting ? 'Yes' : 'No'}
Results: + {results.length > 0 ? ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ ) : ( + 'No results' + )} +
+
+ )} +
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useAsyncThrottler/src/index.tsx b/examples/preact/useAsyncThrottler/src/index.tsx index 715d8e3e..63589e36 100644 --- a/examples/preact/useAsyncThrottler/src/index.tsx +++ b/examples/preact/useAsyncThrottler/src/index.tsx @@ -41,23 +41,21 @@ function App() { } // hook that gives you an async throttler instance - const setSearchAsyncThrottler = useAsyncThrottler( - handleSearch, - { - // leading: true, // default - // trailing: true, // default - wait: 1000, // Wait 1 second between API calls - onError: (error) => { - // optional error handler - console.error('Search failed:', error) - setError(error as Error) - setResults([]) - }, - // throwOnError: true, + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setSearchAsyncThrottler = useAsyncThrottler(handleSearch, { + // leading: true, // default + // trailing: true, // default + wait: 1000, // Wait 1 second between API calls + onError: (error) => { + // optional error handler + console.error('Search failed:', error) + setError(error as Error) + setResults([]) }, - // Optional Selector function to pick the state you want to track and use - (state) => state, - ) + // throwOnError: true, + }) + // Alternative to setSearchAsyncThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, // get and name our throttled function const handleSearchThrottled = setSearchAsyncThrottler.maybeExecute @@ -88,24 +86,38 @@ function App() { {error &&
Error: {error.message}
} -
-

API calls made: {setSearchAsyncThrottler.state.successCount}

- {results.length > 0 && ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
+ ({ + isExecuting: state.isExecuting, + isPending: state.isPending, + successCount: state.successCount, + })} + > + {({ isExecuting, isPending, successCount }) => ( +
+

API calls made: {successCount}

+ {results.length > 0 && ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ )} + {isPending ? ( +

Pending...

+ ) : isExecuting ? ( +

Executing...

+ ) : null} +
)} - {setSearchAsyncThrottler.state.isPending ? ( -

Pending...

- ) : setSearchAsyncThrottler.state.isExecuting ? ( -

Executing...

- ) : null} -
-          {JSON.stringify(setSearchAsyncThrottler.store.state, null, 2)}
-        
-
+ + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useBatcher/src/index.tsx b/examples/preact/useBatcher/src/index.tsx index 1e7c3098..db4aae53 100644 --- a/examples/preact/useBatcher/src/index.tsx +++ b/examples/preact/useBatcher/src/index.tsx @@ -15,69 +15,81 @@ function App1() { console.log('processing batch', items) } - const batcher = useBatcher( - processBatch, - { - // started: false, // true by default - maxSize: 5, // Process in batches of 5 (if comes before wait time) - wait: 3000, // wait up to 3 seconds before processing a batch (if time elapses before maxSize is reached) - getShouldExecute: (items, _batcher) => items.includes(42), // or pass in a custom function to determine if the batch should be processed - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - executionCount: state.executionCount, - totalItemsProcessed: state.totalItemsProcessed, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const batcher = useBatcher(processBatch, { + // started: false, // true by default + maxSize: 5, // Process in batches of 5 (if comes before wait time) + wait: 3000, // wait up to 3 seconds before processing a batch (if time elapses before maxSize is reached) + getShouldExecute: (items, _batcher) => items.includes(42), // or pass in a custom function to determine if the batch should be processed + }) + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, return (

TanStack Pacer useBatcher Example 1

-
Batch Size: {batcher.state.size}
-
Batch Max Size: {3}
-
Batch Items: {batcher.peekAllItems().join(', ')}
-
Batches Processed: {batcher.state.executionCount}
-
Items Processed: {batcher.state.totalItemsProcessed}
-
- Processed Batches:{' '} - {processedBatches.map((b, i) => ( + ({ + size: state.size, + executionCount: state.executionCount, + totalItemsProcessed: state.totalItemsProcessed, + })} + > + {({ size, executionCount, totalItemsProcessed }) => ( <> - [{b.join(', ')}],{' '} +
Batch Size: {size}
+
Batch Max Size: {3}
+
Batch Items: {batcher.peekAllItems().join(', ')}
+
Batches Processed: {executionCount}
+
Items Processed: {totalItemsProcessed}
+
+ Processed Batches:{' '} + {processedBatches.map((b, i) => ( + <> + [{b.join(', ')}],{' '} + + ))} +
+
+ + +
- ))} -
-
- - -
-
-        {JSON.stringify(batcher.store.state, null, 2)}
-      
+ )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useDebouncedState/src/index.tsx b/examples/preact/useDebouncedState/src/index.tsx index 083cf825..9aa7323c 100644 --- a/examples/preact/useDebouncedState/src/index.tsx +++ b/examples/preact/useDebouncedState/src/index.tsx @@ -14,12 +14,9 @@ function App1() { wait: 500, // enabled: () => instantCount > 2, // optional, defaults to true // leading: true, // optional, defaults to false + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), ) function increment() { @@ -36,35 +33,50 @@ function App1() {

TanStack Pacer useDebouncedState Example 1

- - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Execution Count:{debouncer.state.executionCount}
-
-
Instant Count:{instantCount}
Debounced Count:{debouncedCount}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}
+
+
Instant Count:{instantCount}
Debounced Count:{debouncedCount}
-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -78,12 +90,9 @@ function App2() { { wait: 500, enabled: instantSearch.length > 2, // optional, defaults to true + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), ) function handleSearchChange(e: JSX.TargetedEvent) { @@ -107,32 +116,47 @@ function App2() { - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Execution Count:{debouncer.state.executionCount}
-
-
Instant Search:{instantSearch}
Debounced Search:{debouncedSearch}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}
+
+
Instant Search:{instantSearch}
Debounced Search:{debouncedSearch}
-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -146,12 +170,9 @@ function App3() { currentValue, { wait: 250, + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -194,43 +215,58 @@ function App3() { - - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - debouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Debounced to 250ms wait time

-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useDebouncedValue/src/index.tsx b/examples/preact/useDebouncedValue/src/index.tsx index 048a2e08..e57b63a7 100644 --- a/examples/preact/useDebouncedValue/src/index.tsx +++ b/examples/preact/useDebouncedValue/src/index.tsx @@ -11,16 +11,13 @@ function App1() { } // highest-level hook that watches an instant local state value and returns a debounced value - const [debouncedCount] = useDebouncedValue( - instantCount, - { - wait: 500, - // enabled: () => instantCount > 2, // optional, defaults to true - // leading: true, // optional, defaults to false - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [debouncedCount] = useDebouncedValue(instantCount, { + wait: 500, + // enabled: () => instantCount > 2, // optional, defaults to true + // leading: true, // optional, defaults to false + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) return (
@@ -49,15 +46,12 @@ function App2() { // highest-level hook that watches an instant local state value and returns a debounced value // optionally, grab the debouncer from the last index of the returned array - const [debouncedSearch] = useDebouncedValue( - instantSearch, - { - wait: 500, - enabled: instantSearch.length > 2, // optional, defaults to true - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [debouncedSearch] = useDebouncedValue(instantSearch, { + wait: 500, + enabled: instantSearch.length > 2, // optional, defaults to true + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleSearchChange(e: JSX.TargetedEvent) { setInstantSearch(e.currentTarget.value) @@ -97,17 +91,11 @@ function App3() { const [instantExecutionCount, setInstantExecutionCount] = useState(0) // highest-level hook that watches an instant local state value and returns a debounced value - const [debouncedValue, debouncer] = useDebouncedValue( - currentValue, - { - wait: 250, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), - ) + const [debouncedValue, debouncer] = useDebouncedValue(currentValue, { + wait: 250, + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -146,45 +134,60 @@ function App3() { {debouncedValue}
- - - - - - - - - - - - - - - - - - - - - - - -
Is Pending:{debouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - debouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
-
-

Debounced to 250ms wait time

-
-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + +
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
+
+

Debounced to 250ms wait time

+
+ + )} +
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useDebouncer/src/index.tsx b/examples/preact/useDebouncer/src/index.tsx index 0a9d9b99..a0cbc5da 100644 --- a/examples/preact/useDebouncer/src/index.tsx +++ b/examples/preact/useDebouncer/src/index.tsx @@ -10,19 +10,14 @@ function App1() { const [debouncedCount, setDebouncedCount] = useState(0) // Lower-level useDebouncer hook - requires you to manage your own state - const debouncer = useDebouncer( - setDebouncedCount, - { - wait: 800, - enabled: () => instantCount > 2, // optional, defaults to true - // leading: true, // optional, defaults to false - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - status: state.status, - executionCount: state.executionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const debouncer = useDebouncer(setDebouncedCount, { + wait: 800, + enabled: () => instantCount > 2, // optional, defaults to true + // leading: true, // optional, defaults to false + }) + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => ({ status: state.status, executionCount: state.executionCount }), function increment() { // this pattern helps avoid common bugs with stale closures and state @@ -38,14 +33,25 @@ function App1() {

TanStack Pacer useDebouncer Example 1

- - - - - - - - + ({ + status: state.status, + executionCount: state.executionCount, + })} + > + {({ status, executionCount }) => ( + <> + + + + + + + + + + )} +
Status:{debouncer.state.status}
Execution Count:{debouncer.state.executionCount}
Status:{status}
Execution Count:{executionCount}

@@ -70,9 +76,13 @@ function App1() { Flush -
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -82,18 +92,13 @@ function App2() { const [debouncedSearchText, setDebouncedSearchText] = useState('') // Lower-level useDebouncer hook - requires you to manage your own state - const setSearchDebouncer = useDebouncer( - setDebouncedSearchText, - { - wait: 500, - enabled: () => searchText.length > 2, // optional, defaults to true - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setSearchDebouncer = useDebouncer(setDebouncedSearchText, { + wait: 500, + enabled: () => searchText.length > 2, // optional, defaults to true + }) + // Alternative to setSearchDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => ({ isPending: state.isPending, executionCount: state.executionCount }), function handleSearchChange(e: JSX.TargetedEvent) { const newValue = e.currentTarget.value @@ -116,14 +121,25 @@ function App2() { - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + )} +
Is Pending:{setSearchDebouncer.state.isPending.toString()}
Execution Count:{setSearchDebouncer.state.executionCount}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}

@@ -142,9 +158,13 @@ function App2() {
-
-        {JSON.stringify(setSearchDebouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -155,17 +175,12 @@ function App3() { const [instantExecutionCount, setInstantExecutionCount] = useState(0) // Lower-level useDebouncer hook - requires you to manage your own state - const setValueDebouncer = useDebouncer( - setDebouncedValue, - { - wait: 250, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setValueDebouncer = useDebouncer(setDebouncedValue, { + wait: 250, + }) + // Alternative to setValueDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => ({ isPending: state.isPending, executionCount: state.executionCount }), function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -207,38 +222,46 @@ function App3() { - - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{setValueDebouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{setValueDebouncer.state.executionCount}
Saved Executions: - {instantExecutionCount - setValueDebouncer.state.executionCount} -
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - setValueDebouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -247,9 +270,13 @@ function App3() {
-
-        {JSON.stringify(setValueDebouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useQueuedState/src/index.tsx b/examples/preact/useQueuedState/src/index.tsx index b06cb4f7..78773367 100644 --- a/examples/preact/useQueuedState/src/index.tsx +++ b/examples/preact/useQueuedState/src/index.tsx @@ -9,89 +9,100 @@ function App1() { console.log('processing item', item) } - const [queueItems, addItem, queuer] = useQueuedState( - processItem, - { - maxSize: 25, - initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - started: false, - wait: 1000, // wait 1 second between processing items - wait is optional! - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - items: state.items, // required for useQueuedState - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - executionCount: state.executionCount, - isRunning: state.isRunning, - }), - ) + // Note: useQueuedState requires items in selector, but we'll use Subscribe for reactive rendering + const [queueItems, addItem, queuer] = useQueuedState(processItem, { + maxSize: 25, + initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + started: false, + wait: 1000, // wait 1 second between processing items - wait is optional! + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) return (

TanStack Pacer useQueuedState Example 1

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queueItems.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + isRunning: state.isRunning, + })} > - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + executionCount, + isRunning, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {queueItems.join(', ')}
+
+ + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -110,17 +121,9 @@ function App2() { maxSize: 100, started: true, wait: 100, + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - (state) => ({ - items: state.items, // required for useQueuedState - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - executionCount: state.executionCount, - isRunning: state.isRunning, - }), ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -163,59 +166,78 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, status, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.status}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{status}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Queued with 100ms wait time

-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useQueuedValue/src/index.tsx b/examples/preact/useQueuedValue/src/index.tsx index 1647f4a0..6c15a4c9 100644 --- a/examples/preact/useQueuedValue/src/index.tsx +++ b/examples/preact/useQueuedValue/src/index.tsx @@ -7,87 +7,97 @@ function App1() { const [instantSearchValue, setInstantSearchValue] = useState('') // Queuer that processes a single value with delays - const [value, queuer] = useQueuedValue( - instantSearchValue, - { - maxSize: 25, - wait: 500, // wait 500ms between processing value changes - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - isRunning: state.isRunning, - items: state.items, // required for useQueuedValue hook - size: state.size, - status: state.status, - }), - ) + const [value, queuer] = useQueuedValue(instantSearchValue, { + maxSize: 25, + wait: 500, // wait 500ms between processing value changes + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) return (

TanStack Pacer useQueuedValue Example 1

Current Value: {value}

-
Queue Size: {queuer.state.size}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.peekAllItems().join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + status: state.status, + executionCount: state.executionCount, + })} > - { - setInstantSearchValue(e.currentTarget.value) // instantly update the local search value - }} - placeholder="Enter search term..." - disabled={queuer.state.isFull} - /> - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + isRunning, + status, + executionCount, + }) => ( + <> +
Queue Size: {size}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {queuer.peekAllItems().join(', ')}
+
+ { + setInstantSearchValue(e.currentTarget.value) // instantly update the local search value + }} + placeholder="Enter search term..." + disabled={isFull} + /> + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -97,23 +107,12 @@ function App2() { const [instantExecutionCount, setInstantExecutionCount] = useState(0) // Queuer that processes a single value with delays - const [queuedValue, queuer] = useQueuedValue( - currentValue, - { - maxSize: 100, - wait: 100, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - items: state.items, // required for useQueuedValue hook - size: state.size, - status: state.status, - }), - ) + const [queuedValue, queuer] = useQueuedValue(currentValue, { + maxSize: 100, + wait: 100, + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -154,59 +153,78 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, status, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.status}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{status}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Queued with 100ms wait time

-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useQueuer/src/index.tsx b/examples/preact/useQueuer/src/index.tsx index dc4340a6..3c5e7221 100644 --- a/examples/preact/useQueuer/src/index.tsx +++ b/examples/preact/useQueuer/src/index.tsx @@ -12,94 +12,107 @@ function App1() { console.log('processing item', item) } - const queuer = useQueuer( - processItem, - { - key: 'Add Number Queue', - initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - maxSize: 25, // optional, defaults to Infinity - started: false, // optional, defaults to true - wait: 1000, // wait 1 second between processing items - wait is optional! - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - status: state.status, - executionCount: state.executionCount, - items: state.items, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const queuer = useQueuer(processItem, { + key: 'Add Number Queue', + initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + maxSize: 25, // optional, defaults to Infinity + started: false, // optional, defaults to true + wait: 1000, // wait 1 second between processing items - wait is optional! + }) + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, return (

TanStack Pacer useQueuer Example 1

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.state.items.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + status: state.status, + executionCount: state.executionCount, + items: state.items, + })} > - - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + isRunning, + status, + executionCount, + items, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {items.join(', ')}
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -113,24 +126,15 @@ function App2() { setQueuedValue(item) } - const queuer = useQueuer( - processItem, - { - key: 'Range Queue', - maxSize: 100, - initialItems: [currentValue], - wait: 100, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - executionCount: state.executionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const queuer = useQueuer(processItem, { + key: 'Range Queue', + maxSize: 100, + initialItems: [currentValue], + wait: 100, + }) + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -172,51 +176,66 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, isRunning, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -225,9 +244,13 @@ function App2() {
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useQueuerWithPersister/src/index.tsx b/examples/preact/useQueuerWithPersister/src/index.tsx index fb05255a..410100b1 100644 --- a/examples/preact/useQueuerWithPersister/src/index.tsx +++ b/examples/preact/useQueuerWithPersister/src/index.tsx @@ -13,7 +13,6 @@ function App1() { // maxAge: 1000 * 60, // 1 minute // buster: 'v1', // }) - // const queuerPersister = undefined as any // The function that we will be queuing function processItem(item: number) { @@ -24,85 +23,109 @@ function App1() { processItem, { initialItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - // initialState: queuerPersister?.loadState(), + // initialState: queuerPersister.loadState(), maxSize: 25, // optional, defaults to Infinity started: false, // optional, defaults to true wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // useEffect(() => { - // queuerPersister?.saveState(queuer.state) - // }, [queuer.state]) + // queuerPersister.saveState(queuer.store.state) + // }, [queuer.store.state]) return (

TanStack Pacer useQueuer Example 1 (with persister)

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.state.items.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + status: state.status, + executionCount: state.executionCount, + items: state.items, + })} > - - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + isRunning, + status, + executionCount, + items, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {items.join(', ')}
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -123,15 +146,8 @@ function App2() { initialItems: [currentValue], wait: 100, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - executionCount: state.executionCount, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -174,51 +190,66 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, isRunning, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -227,9 +258,13 @@ function App2() {
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useRateLimitedState/src/index.tsx b/examples/preact/useRateLimitedState/src/index.tsx index ec79ad65..a8c254cf 100644 --- a/examples/preact/useRateLimitedState/src/index.tsx +++ b/examples/preact/useRateLimitedState/src/index.tsx @@ -20,12 +20,9 @@ function App1() { 'Rejected by rate limiter', rateLimiter.getMsUntilNextWindow(), ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), ) function increment() { @@ -64,22 +61,33 @@ function App1() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
@@ -89,9 +97,13 @@ function App1() {
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -113,12 +125,9 @@ function App2() { 'Rejected by rate limiter', rateLimiter.getMsUntilNextWindow(), ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), ) function handleSearchChange(e: JSX.TargetedEvent) { @@ -164,22 +173,33 @@ function App2() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Instant Search:{instantSearch}
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Instant Search:{instantSearch}
Rate Limited Search:{limitedSearch}
@@ -188,9 +208,13 @@ function App2() {
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -212,12 +236,9 @@ function App3() { 'Rejected by rate limiter', rateLimiter.getMsUntilNextWindow(), ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -282,52 +303,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useRateLimitedValue/src/index.tsx b/examples/preact/useRateLimitedValue/src/index.tsx index 0c976eec..67c13fd2 100644 --- a/examples/preact/useRateLimitedValue/src/index.tsx +++ b/examples/preact/useRateLimitedValue/src/index.tsx @@ -9,22 +9,19 @@ function App1() { // Using useRateLimitedValue with a rate limit of 5 executions per 5 seconds // optionally, grab the rate limiter from the last index of the returned array - const [limitedCount] = useRateLimitedValue( - instantCount, - { - // enabled: () => instantCount > 2, // optional, defaults to true - limit: 5, - window: 5000, - windowType: windowType, - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [limitedCount] = useRateLimitedValue(instantCount, { + // enabled: () => instantCount > 2, // optional, defaults to true + limit: 5, + window: 5000, + windowType: windowType, + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function increment() { setInstantCount((c) => c + 1) @@ -79,22 +76,19 @@ function App2() { const [instantSearch, setInstantSearch] = useState('') // Using useRateLimitedValue with a rate limit of 5 executions per 5 seconds - const [limitedSearch] = useRateLimitedValue( - instantSearch, - { - // enabled: instantSearch.length > 2, // optional, defaults to true - limit: 5, - window: 5000, - windowType: windowType, - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [limitedSearch] = useRateLimitedValue(instantSearch, { + // enabled: instantSearch.length > 2, // optional, defaults to true + limit: 5, + window: 5000, + windowType: windowType, + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleSearchChange(e: JSX.TargetedEvent) { setInstantSearch(e.currentTarget.value) @@ -157,24 +151,18 @@ function App3() { const [instantExecutionCount, setInstantExecutionCount] = useState(0) // Using useRateLimitedValue with a rate limit of 5 executions per 5 seconds - const [limitedValue, rateLimiter] = useRateLimitedValue( - currentValue, - { - limit: 20, - window: 2000, - windowType: windowType, - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), - ) + const [limitedValue, rateLimiter] = useRateLimitedValue(currentValue, { + limit: 20, + window: 2000, + windowType: windowType, + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -237,52 +225,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useRateLimiter/src/index.tsx b/examples/preact/useRateLimiter/src/index.tsx index 13dcd060..393ae061 100644 --- a/examples/preact/useRateLimiter/src/index.tsx +++ b/examples/preact/useRateLimiter/src/index.tsx @@ -20,24 +20,19 @@ function App1() { const [limitedCount, setLimitedCount] = useState(0) // rate-limited // Using useRateLimiter with a rate limit of 5 executions per 5 seconds - const rateLimiter = useRateLimiter( - setLimitedCount, - { - // enabled: () => instantCount > 2, - ...commonRateLimiterOptions, - windowType: windowType, - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const rateLimiter = useRateLimiter(setLimitedCount, { + // enabled: () => instantCount > 2, + ...commonRateLimiterOptions, + windowType: windowType, + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + }) + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function increment() { // this pattern helps avoid common bugs with stale closures and state @@ -75,44 +70,59 @@ function App1() { - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -122,24 +132,19 @@ function App2() { const [limitedSearch, setLimitedSearch] = useState('') // Using useRateLimiter with a rate limit of 5 executions per 5 seconds - const rateLimiter = useRateLimiter( - setLimitedSearch, - { - enabled: instantSearch.length > 2, // optional, defaults to true - ...commonRateLimiterOptions, - // windowType: 'sliding', // default is 'fixed' - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const rateLimiter = useRateLimiter(setLimitedSearch, { + enabled: instantSearch.length > 2, // optional, defaults to true + ...commonRateLimiterOptions, + // windowType: 'sliding', // default is 'fixed' + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + }) + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function handleSearchChange(e: JSX.TargetedEvent) { const newValue = e.currentTarget.value @@ -162,42 +167,58 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Search:
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Search:{instantSearch}
Rate Limited Search:{limitedSearch}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -208,23 +229,18 @@ function App3() { const [instantExecutionCount, setInstantExecutionCount] = useState(0) // Using useRateLimiter with a rate limit of 5 executions per 5 seconds - const rateLimiter = useRateLimiter( - setLimitedValue, - { - limit: 20, - window: 2000, - onReject: (rateLimiter) => - console.log( - 'Rejected by rate limiter', - rateLimiter.getMsUntilNextWindow(), - ), - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const rateLimiter = useRateLimiter(setLimitedValue, { + limit: 20, + window: 2000, + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), + }) + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -266,52 +282,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useRateLimiterWithPersister/src/index.tsx b/examples/preact/useRateLimiterWithPersister/src/index.tsx index abb1c0ea..5d39e0e1 100644 --- a/examples/preact/useRateLimiterWithPersister/src/index.tsx +++ b/examples/preact/useRateLimiterWithPersister/src/index.tsx @@ -18,7 +18,6 @@ function App1() { // maxAge: 1000 * 60, // 1 minute // buster: 'v1', // }) - // const rateLimiterPersister = undefined as any // Using useRateLimiter with a rate limit of 5 executions per 5 seconds const rateLimiter = useRateLimiter( @@ -34,15 +33,15 @@ function App1() { rateLimiter.getMsUntilNextWindow(), ), // optional local storage persister to retain state on page refresh - // initialState: rateLimiterPersister?.loadState(), + // initialState: rateLimiterPersister.loadState(), }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // useEffect(() => { - // rateLimiterPersister?.saveState(rateLimiter.state) - // }, [rateLimiter.state]) + // rateLimiterPersister.saveState(rateLimiter.store.state) + // }, [rateLimiter.store.state]) function increment() { // this pattern helps avoid common bugs with stale closures and state @@ -80,44 +79,59 @@ function App1() { - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -140,11 +154,8 @@ function App2() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: JSX.TargetedEvent) { @@ -168,42 +179,57 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Search:
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Search:
Rate Limited Search:{limitedSearch}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -225,11 +251,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -272,52 +295,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useThrottledState/src/index.tsx b/examples/preact/useThrottledState/src/index.tsx index 8bbff926..b11e8cc8 100644 --- a/examples/preact/useThrottledState/src/index.tsx +++ b/examples/preact/useThrottledState/src/index.tsx @@ -13,11 +13,9 @@ function App1() { { wait: 1000, // enabled: () => instantCount > 2, // optional, defaults to true + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), ) function increment() { @@ -34,26 +32,38 @@ function App1() {

TanStack Pacer useThrottledState Example 1

- - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{throttler.state.executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
Execution Count:{executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -67,11 +77,9 @@ function App2() { { wait: 1000, // enabled: instantSearch.length > 2, // optional, defaults to true + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), ) function handleSearchChange(e: JSX.TargetedEvent) { @@ -95,23 +103,35 @@ function App2() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{throttler.state.executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
Execution Count:{executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -125,11 +145,9 @@ function App3() { currentValue, { wait: 250, + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -172,36 +190,48 @@ function App3() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{throttler.state.executionCount}
Saved Executions: - {instantExecutionCount - throttler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - throttler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +

Throttled to 1 update per 250ms

-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useThrottledValue/src/index.tsx b/examples/preact/useThrottledValue/src/index.tsx index 5761ad36..eafd247e 100644 --- a/examples/preact/useThrottledValue/src/index.tsx +++ b/examples/preact/useThrottledValue/src/index.tsx @@ -12,15 +12,12 @@ function App1() { // highest-level hook that watches an instant local state value and returns a throttled value // optionally, grab the throttler from the last index of the returned array - const [throttledCount] = useThrottledValue( - instantCount, - { - wait: 1000, - // enabled: () => instantCount > 2, // optional, defaults to true - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [throttledCount] = useThrottledValue(instantCount, { + wait: 1000, + // enabled: () => instantCount > 2, // optional, defaults to true + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) return (
@@ -48,15 +45,12 @@ function App2() { const [instantSearch, setInstantSearch] = useState('') // highest-level hook that watches an instant local state value and returns a throttled value - const [throttledSearch] = useThrottledValue( - instantSearch, - { - wait: 1000, - // enabled: instantSearch.length > 2, // optional, defaults to true - }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example - ) + const [throttledSearch] = useThrottledValue(instantSearch, { + wait: 1000, + // enabled: instantSearch.length > 2, // optional, defaults to true + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleSearchChange(e: JSX.TargetedEvent) { setInstantSearch(e.currentTarget.value) @@ -96,16 +90,11 @@ function App3() { const [currentValue, setCurrentValue] = useState(50) // highest-level hook that watches an instant local state value and returns a throttled value - const [throttledValue, throttler] = useThrottledValue( - currentValue, - { - wait: 250, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), - ) + const [throttledValue, throttler] = useThrottledValue(currentValue, { + wait: 250, + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, + }) function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -146,36 +135,48 @@ function App3() {
- - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{throttler.state.executionCount}
Saved Executions: - {instantExecutionCount - throttler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - throttler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +

Throttled to 1 update per 250ms

-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/useThrottler/src/index.tsx b/examples/preact/useThrottler/src/index.tsx index c600f191..4303e6ff 100644 --- a/examples/preact/useThrottler/src/index.tsx +++ b/examples/preact/useThrottler/src/index.tsx @@ -10,17 +10,15 @@ function App1() { const [throttledCount, setThrottledCount] = useState(0) // Lower-level useThrottler hook - requires you to manage your own state - const setCountThrottler = useThrottler( - setThrottledCount, - { - wait: 1000, - // leading: true, // default - // trailing: true, // default - // enabled: () => instantCount > 2, - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ executionCount: state.executionCount }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setCountThrottler = useThrottler(setThrottledCount, { + wait: 1000, + // leading: true, // default + // trailing: true, // default + // enabled: () => instantCount > 2, + }) + // Alternative to setCountThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function increment() { // this pattern helps avoid common bugs with stale closures and state @@ -36,18 +34,26 @@ function App1() {

TanStack Pacer useThrottler Example 1

- - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{setCountThrottler.state.executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
Execution Count:{executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
@@ -59,9 +65,13 @@ function App1() { Flush
-
-        {JSON.stringify(setCountThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -71,16 +81,13 @@ function App2() { const [throttledSearch, setThrottledSearch] = useState('') // Lower-level useThrottler hook - requires you to manage your own state - const setSearchThrottler = useThrottler( - setThrottledSearch, - { - wait: 1000, - enabled: instantSearch.length > 2, - }, - // Optional Selector function to pick the state you want to track and use - - (state) => ({ executionCount: state.executionCount }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setSearchThrottler = useThrottler(setThrottledSearch, { + wait: 1000, + enabled: instantSearch.length > 2, + }) + // Alternative to setSearchThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function handleSearchChange(e: JSX.TargetedEvent) { const newValue = e.currentTarget.value @@ -103,26 +110,38 @@ function App2() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{setSearchThrottler.state.executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
Execution Count:{executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
-
-        {JSON.stringify(setSearchThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -133,16 +152,14 @@ function App3() { const [throttledValue, setThrottledValue] = useState(50) // Lower-level useThrottler hook - requires you to manage your own state - const setValueThrottler = useThrottler( - setThrottledValue, - { - wait: 250, - // leading: true, // default - // trailing: true, // default - }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ executionCount: state.executionCount }), - ) + // No selector needed - we'll use Subscribe HOC to subscribe to state in the component tree + const setValueThrottler = useThrottler(setThrottledValue, { + wait: 250, + // leading: true, // default + // trailing: true, // default + }) + // Alternative to setValueThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, function handleRangeChange(e: JSX.TargetedEvent) { const newValue = parseInt(e.currentTarget.value, 10) @@ -188,29 +205,36 @@ function App3() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{setValueThrottler.state.executionCount}
Saved Executions: - {instantExecutionCount - setValueThrottler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - - setValueThrottler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +
@@ -219,9 +243,13 @@ function App3() {
-
-        {JSON.stringify(setValueThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/preact/util-comparison/src/index.tsx b/examples/preact/util-comparison/src/index.tsx index 57ffe3ac..c3484a4f 100644 --- a/examples/preact/util-comparison/src/index.tsx +++ b/examples/preact/util-comparison/src/index.tsx @@ -4,8 +4,8 @@ import type { JSX } from 'preact' import { useDebouncer } from '@tanstack/preact-pacer/debouncer' import { useThrottler } from '@tanstack/preact-pacer/throttler' import { useRateLimiter } from '@tanstack/preact-pacer/rate-limiter' -import { useQueuer } from '@tanstack/preact-pacer/queuer' -import { useBatcher } from '@tanstack/preact-pacer/batcher' +import { QueuerState, useQueuer } from '@tanstack/preact-pacer/queuer' +import { BatcherState, useBatcher } from '@tanstack/preact-pacer/batcher' import { pacerDevtoolsPlugin } from '@tanstack/preact-pacer-devtools' import { TanStackDevtools } from '@tanstack/preact-devtools' @@ -27,7 +27,8 @@ function ComparisonApp() { key: 'my-debouncer', wait: 600, }, - (state) => state, + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const throttler = useThrottler( @@ -36,7 +37,8 @@ function ComparisonApp() { key: 'my-throttler', wait: 600, }, - (state) => state, + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const rateLimiter = useRateLimiter( @@ -47,7 +49,8 @@ function ComparisonApp() { window: 2000, windowType: 'sliding', }, - (state) => state, + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const queuer = useQueuer( @@ -57,7 +60,8 @@ function ComparisonApp() { wait: 100, maxSize: 50, }, - (state) => state, + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const batcher = useBatcher( @@ -72,7 +76,8 @@ function ComparisonApp() { wait: 600, maxSize: 5, }, - (state) => state, + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: JSX.TargetedEvent) { @@ -89,13 +94,18 @@ function ComparisonApp() { } // Helper function to determine sync status - function getSyncStatus(processedValue: number, utilityName: string) { + // Note: This function will be called from within Subscribe HOCs, so it receives state as parameter + function getSyncStatus( + processedValue: number, + utilityName: string, + utilityState?: any, + ) { const isOutOfSync = processedValue !== currentValue const isPending = - (utilityName === 'Debouncer' && debouncer.state.status === 'pending') || - (utilityName === 'Throttler' && throttler.state.status === 'pending') || - (utilityName === 'Queuer' && queuer.state.status === 'running') || - (utilityName === 'Batcher' && batcher.state.status === 'pending') + (utilityName === 'Debouncer' && utilityState?.status === 'pending') || + (utilityName === 'Throttler' && utilityState?.status === 'pending') || + (utilityName === 'Queuer' && utilityState?.status === 'running') || + (utilityName === 'Batcher' && utilityState?.status === 'pending') // Tooltip explanations for why certain utilities become out of sync const getTooltip = () => { @@ -127,78 +137,46 @@ function ComparisonApp() { } } - // Warning icon SVG - const WarningIcon = ({ size = 16 }: { size?: number }) => ( - - - - - - ) - - // Success icon SVG - const SuccessIcon = ({ size = 16 }: { size?: number }) => ( - - - - - ) - - const utilityData = [ + // Utility metadata (without state - state will be accessed via Subscribe HOCs) + const utilityMetadata = [ { name: 'Debouncer', value: debouncedValue, - state: debouncer.state, description: `Delays execution until after ${debouncer.options.wait}ms of inactivity`, color: '#3b82f6', // blue flush: () => debouncer.flush(), + util: debouncer, }, { name: 'Throttler', value: throttledValue, - state: throttler.state, description: `Limits execution to once every ${throttler.options.wait}ms`, color: '#0891b2', // cyan flush: () => throttler.flush(), + util: throttler, }, { name: 'Rate Limiter', value: rateLimitedValue, - state: rateLimiter.state, description: `Allows max ${rateLimiter.options.limit} executions per ${rateLimiter.options.window}ms window`, color: '#ea580c', // orange + util: rateLimiter, }, { name: 'Queuer', value: queuedValue, - state: queuer.state, description: `Processes items sequentially with ${queuer.options.wait}ms delay`, color: '#db2777', // pink flush: () => queuer.flush(), + util: queuer, }, { name: 'Batcher', value: batchedValue, - state: batcher.state, description: `Processes in batches of ${batcher.options.maxSize} or after ${batcher.options.wait}ms`, color: '#8b5cf6', // purple flush: () => batcher.flush(), + util: batcher, }, ] as const @@ -244,169 +222,181 @@ function ComparisonApp() { marginBottom: '30px', }} > - {utilityData.map((utility) => { - const syncStatus = getSyncStatus(utility.value, utility.name) - return ( -
-

- {utility.name} -

-

- {utility.description} -

- -
-
- - Value: {utility.value} - -
-
- {syncStatus.isOutOfSync ? ( - - - {syncStatus.statusText} - - ) : ( - - - {syncStatus.statusText} - - )} -
- - alert( - 'These sliders are read-only. Move the main slider at the top', - ) - } - type="range" - min="0" - max="100" - value={utility.value} - readOnly + {utilityMetadata.map((utility) => ( + state} + > + {(state) => { + const syncStatus = getSyncStatus( + utility.value, + utility.name, + state, + ) + return ( +
-
+ > +

+ {utility.name} +

+

+ {utility.description} +

-
-
- Executions: {utility.state.executionCount} -
-
- Reduction:{' '} - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - utility.state.executionCount) / - instantExecutionCount) * - 100, +
+
+ + Value: {utility.value} + +
+
+ {syncStatus.isOutOfSync ? ( + + + {syncStatus.statusText} + + ) : ( + + + {syncStatus.statusText} + )} - % -
- {utility.name === 'Rate Limiter' && ( -
- Rejections:{' '} - {(utility.state as any).rejectionCount} +
+ + alert( + 'These sliders are read-only. Move the main slider at the top', + ) + } + type="range" + min="0" + max="100" + value={utility.value} + readOnly + style={{ + width: '100%', + margin: '2px 0', + accentColor: utility.color, + }} + />
- )} - {utility.name === 'Queuer' && ( - <> + +
- Queue Size: {utility.state.size} + Executions: {state.executionCount}
- - )} - {utility.name === 'Batcher' && ( - <>
- Batch Size: {utility.state.size} + Reduction:{' '} + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - state.executionCount) / + instantExecutionCount) * + 100, + )} + %
+ {utility.name === 'Rate Limiter' && ( +
+ Rejections:{' '} + {(state as any).rejectionCount} +
+ )} + {utility.name === 'Queuer' && ( + <> +
+ Queue Size:{' '} + {(state as QueuerState).size} +
+ + )} + {utility.name === 'Batcher' && ( + <> +
+ Batch Size:{' '} + {(state as BatcherState).size} +
+
+ Items Processed:{' '} + {(state as any).totalItemsProcessed} +
+ + )}
- Items Processed:{' '} - {(utility.state as any).totalItemsProcessed} + Status: {state.status}
- - )} -
- Status: {utility.state.status} +
+ {'flush' in utility && + typeof utility.flush === 'function' && ( + + )}
-
- {'flush' in utility && typeof utility.flush === 'function' && ( - - )} -
- ) - })} + ) + }} +
+ ))}
@@ -420,31 +410,38 @@ function ComparisonApp() { gap: '8px', }} > - {utilityData.map((utility) => ( -
-

- {utility.name} State -

-
-                {JSON.stringify(utility.state, null, 2)}
-              
-
+ {utilityMetadata.map((utility) => ( + state} + > + {(state) => ( +
+

+ {utility.name} State +

+
+                    {JSON.stringify(state, null, 2)}
+                  
+
+ )} +
))}
@@ -460,3 +457,36 @@ function ComparisonApp() { const root = document.getElementById('root')! render(, root) + +// Warning icon SVG +const WarningIcon = ({ size = 16 }: { size?: number }) => ( + + + + + +) + +// Success icon SVG +const SuccessIcon = ({ size = 16 }: { size?: number }) => ( + + + + +) diff --git a/examples/react/react-query-debounced-prefetch/src/index.tsx b/examples/react/react-query-debounced-prefetch/src/index.tsx index ec9e8299..187be920 100644 --- a/examples/react/react-query-debounced-prefetch/src/index.tsx +++ b/examples/react/react-query-debounced-prefetch/src/index.tsx @@ -42,6 +42,8 @@ function PostList({ // debounce the hovered post id to avoid excessive prefetches const [debouncedHoveredPostId] = useDebouncedValue(currentHoveredPostId, { wait: 100, // adjust this value to see the difference + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }) // perform the prefetch when the debounced hovered post id changes diff --git a/examples/react/react-query-queued-prefetch/src/index.tsx b/examples/react/react-query-queued-prefetch/src/index.tsx index 865ea21c..3f507042 100644 --- a/examples/react/react-query-queued-prefetch/src/index.tsx +++ b/examples/react/react-query-queued-prefetch/src/index.tsx @@ -46,6 +46,8 @@ function PostList({ onExpire: (item) => { console.log('expired', item) }, + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }) useEffect(() => { diff --git a/examples/react/react-query-throttled-prefetch/src/index.tsx b/examples/react/react-query-throttled-prefetch/src/index.tsx index 4d9b1111..aa74b6dc 100644 --- a/examples/react/react-query-throttled-prefetch/src/index.tsx +++ b/examples/react/react-query-throttled-prefetch/src/index.tsx @@ -42,6 +42,8 @@ function PostList({ // throttle the hovered post id to avoid excessive prefetches const [throttledHoveredPostId] = useThrottledValue(currentHoveredPostId, { wait: 100, // adjust this value to see the difference + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, }) // perform the prefetch when the throttled hovered post id changes diff --git a/examples/react/useAsyncBatcher/src/index.tsx b/examples/react/useAsyncBatcher/src/index.tsx index 253059a6..b2a5c945 100644 --- a/examples/react/useAsyncBatcher/src/index.tsx +++ b/examples/react/useAsyncBatcher/src/index.tsx @@ -70,15 +70,8 @@ function App() { ) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isExecuting: state.isExecuting, - status: state.status, - successCount: state.successCount, - errorCount: state.errorCount, - totalItemsProcessed: state.totalItemsProcessed, - }), + // Alternative to asyncBatcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const addItem = (isUrgent = false) => { @@ -104,18 +97,36 @@ function App() {

TanStack Pacer useAsyncBatcher Example

-
-

Batch Status

-
Current Batch Size: {asyncBatcher.state.size}
-
Max Batch Size: 5
-
Is Executing: {asyncBatcher.state.isExecuting ? 'Yes' : 'No'}
-
Status: {asyncBatcher.state.status}
-
Successful Batches: {asyncBatcher.state.successCount}
-
Failed Batches: {asyncBatcher.state.errorCount}
-
- Total Items Processed: {asyncBatcher.state.totalItemsProcessed} -
-
+ ({ + size: state.size, + isExecuting: state.isExecuting, + status: state.status, + successCount: state.successCount, + errorCount: state.errorCount, + totalItemsProcessed: state.totalItemsProcessed, + })} + > + {({ + size, + isExecuting, + status, + successCount, + errorCount, + totalItemsProcessed, + }) => ( +
+

Batch Status

+
Current Batch Size: {size}
+
Max Batch Size: 5
+
Is Executing: {isExecuting ? 'Yes' : 'No'}
+
Status: {status}
+
Successful Batches: {successCount}
+
Failed Batches: {errorCount}
+
Total Items Processed: {totalItemsProcessed}
+
+ )} +

Current Batch Items

@@ -147,22 +158,29 @@ function App() { - - + {({ size, isExecuting }) => ( + <> + + + + )} +
@@ -195,9 +213,13 @@ function App() { )} -
-        {JSON.stringify(asyncBatcher.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useAsyncDebouncer/src/index.tsx b/examples/react/useAsyncDebouncer/src/index.tsx index aa468506..fe24770a 100644 --- a/examples/react/useAsyncDebouncer/src/index.tsx +++ b/examples/react/useAsyncDebouncer/src/index.tsx @@ -53,12 +53,8 @@ function App() { maxExecutionTime: 1000, }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isExecuting: state.isExecuting, - isPending: state.isPending, - successCount: state.successCount, - }), + // Alternative to asyncDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // get and name our debounced function @@ -89,21 +85,35 @@ function App() {
-
-

API calls made: {asyncDebouncer.state.successCount}

- {results.length > 0 && ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
+ ({ + isExecuting: state.isExecuting, + isPending: state.isPending, + successCount: state.successCount, + })} + > + {({ isExecuting, isPending, successCount }) => ( +
+

API calls made: {successCount}

+ {results.length > 0 && ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ )} + {isPending &&

Pending...

} + {isExecuting &&

Executing...

} +
)} - {asyncDebouncer.state.isPending &&

Pending...

} - {asyncDebouncer.state.isExecuting &&

Executing...

} -
-          {JSON.stringify(asyncDebouncer.store.state, null, 2)}
-        
-
+ + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useAsyncQueuedState/src/index.tsx b/examples/react/useAsyncQueuedState/src/index.tsx index 0c2e5ead..9d90735d 100644 --- a/examples/react/useAsyncQueuedState/src/index.tsx +++ b/examples/react/useAsyncQueuedState/src/index.tsx @@ -39,98 +39,111 @@ function App() { ) // optionally, handle errors here instead of your own try/catch }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - activeItems: state.activeItems, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - isRunning: state.isRunning, - items: state.items, // required for useAsyncQueuedState hook - rejectionCount: state.rejectionCount, - size: state.size, - status: state.status, - successCount: state.successCount, - }), + // Alternative to asyncQueuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return (

TanStack Pacer useAsyncQueuer Example

-
-
Queue Size: {asyncQueuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {asyncQueuer.state.status}
-
Items Processed: {asyncQueuer.state.successCount}
-
Items Rejected: {asyncQueuer.state.rejectionCount}
-
Active Tasks: {asyncQueuer.peekActiveItems().length}
-
Pending Tasks: {asyncQueuer.peekPendingItems().length}
-
- Concurrency:{' '} - - setConcurrency(Math.max(1, parseInt(e.target.value) || 1)) - } - style={{ width: '60px' }} - /> -
-
- Queue Items: - {queueItems.map((item, index) => ( -
- {index}: {item} -
- ))} -
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + successCount: state.successCount, + rejectionCount: state.rejectionCount, + activeItems: state.activeItems, + items: state.items, + isRunning: state.isRunning, + })} > - - - -
- - -
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + successCount, + rejectionCount, + activeItems, + isRunning, + }) => ( + <> +
+
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {successCount}
+
Items Rejected: {rejectionCount}
+
Active Tasks: {activeItems.length}
+
Pending Tasks: {queueItems.length}
+
+ Concurrency:{' '} + + setConcurrency(Math.max(1, parseInt(e.target.value) || 1)) + } + style={{ width: '60px' }} + /> +
+
+ Queue Items: + {queueItems.map((item, index) => ( +
+ {index}: {item} +
+ ))} +
+
+ + + +
+ + +
+ + )} +
-        {JSON.stringify(asyncQueuer.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) diff --git a/examples/react/useAsyncQueuer/src/index.tsx b/examples/react/useAsyncQueuer/src/index.tsx index 4c611363..156e8125 100644 --- a/examples/react/useAsyncQueuer/src/index.tsx +++ b/examples/react/useAsyncQueuer/src/index.tsx @@ -39,105 +39,118 @@ function App() { ) // optionally, handle errors here instead of your own try/catch }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - successCount: state.successCount, - rejectionCount: state.rejectionCount, - activeItems: state.activeItems, - items: state.items, - isRunning: state.isRunning, - }), + // Alternative to asyncQueuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return (

TanStack Pacer useAsyncQueuer Example

-
-
Queue Size: {asyncQueuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {asyncQueuer.state.isFull ? 'Yes' : 'No'}
-
Queue Empty: {asyncQueuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {asyncQueuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {asyncQueuer.state.status}
-
Items Processed: {asyncQueuer.state.successCount}
-
Items Rejected: {asyncQueuer.state.rejectionCount}
-
Active Tasks: {asyncQueuer.state.activeItems.length}
-
Pending Tasks: {asyncQueuer.state.items.length}
-
- Concurrency:{' '} - - setConcurrency(Math.max(1, parseInt(e.target.value) || 1)) - } - style={{ width: '60px' }} - /> -
-
- Queue Items: - {asyncQueuer.peekAllItems().map((item, index) => ( -
- {index}: {item} -
- ))} -
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + successCount: state.successCount, + rejectionCount: state.rejectionCount, + activeItems: state.activeItems, + items: state.items, + isRunning: state.isRunning, + })} > - - - - - - - -
-
-        {JSON.stringify(asyncQueuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + successCount, + rejectionCount, + activeItems, + items, + isRunning, + }) => ( + <> +
+
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {successCount}
+
Items Rejected: {rejectionCount}
+
Active Tasks: {activeItems.length}
+
Pending Tasks: {items.length}
+
+ Concurrency:{' '} + + setConcurrency(Math.max(1, parseInt(e.target.value) || 1)) + } + style={{ width: '60px' }} + /> +
+
+ Queue Items: + {asyncQueuer.peekAllItems().map((item, index) => ( +
+ {index}: {item} +
+ ))} +
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useAsyncRateLimiter/src/index.tsx b/examples/react/useAsyncRateLimiter/src/index.tsx index 2e157f67..ec8027c8 100644 --- a/examples/react/useAsyncRateLimiter/src/index.tsx +++ b/examples/react/useAsyncRateLimiter/src/index.tsx @@ -36,8 +36,6 @@ function App() { const data = await fakeApi(term) setResults(data) setError(null) - - console.log(setSearchAsyncRateLimiter.state.successCount) } // hook that gives you an async rate limiter instance @@ -59,12 +57,8 @@ function App() { setResults([]) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - successCount: state.successCount, - rejectionCount: state.rejectionCount, - isExecuting: state.isExecuting, - }), + // Alternative to setSearchAsyncRateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // get and name our rate limited function @@ -122,43 +116,55 @@ function App() { /> {error &&
Error: {error.message}
} -
- - - - - - - - - - - - - - - - - - - -
API calls made:{setSearchAsyncRateLimiter.state.successCount}
Rejected calls:{setSearchAsyncRateLimiter.state.rejectionCount}
Is executing: - {setSearchAsyncRateLimiter.state.isExecuting ? 'Yes' : 'No'} -
Results: - {results.length > 0 ? ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
- ) : ( - 'No results' - )} -
-
-
-        {JSON.stringify(setSearchAsyncRateLimiter.store.state, null, 2)}
-      
+ ({ + successCount: state.successCount, + rejectionCount: state.rejectionCount, + isExecuting: state.isExecuting, + })} + > + {({ successCount, rejectionCount, isExecuting }) => ( +
+ + + + + + + + + + + + + + + + + + + +
API calls made:{successCount}
Rejected calls:{rejectionCount}
Is executing:{isExecuting ? 'Yes' : 'No'}
Results: + {results.length > 0 ? ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ ) : ( + 'No results' + )} +
+
+ )} +
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useAsyncRateLimiterWithPersister/src/index.tsx b/examples/react/useAsyncRateLimiterWithPersister/src/index.tsx index c9b4cb3c..692fd8ee 100644 --- a/examples/react/useAsyncRateLimiterWithPersister/src/index.tsx +++ b/examples/react/useAsyncRateLimiterWithPersister/src/index.tsx @@ -37,8 +37,6 @@ function App() { const data = await fakeApi(term) setResults(data) setError(null) - - console.log(setSearchAsyncRateLimiter.state.successCount) } const rateLimiterPersister = useStoragePersister< @@ -71,13 +69,13 @@ function App() { // optionally, you can persist the rate limiter state to localStorage initialState: rateLimiterPersister.loadState(), }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to setSearchAsyncRateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) useEffect(() => { - rateLimiterPersister.saveState(setSearchAsyncRateLimiter.state) - }, [setSearchAsyncRateLimiter.state]) + rateLimiterPersister.saveState(setSearchAsyncRateLimiter.store.state) + }, [setSearchAsyncRateLimiter.store.state]) // get and name our rate limited function const handleSearchRateLimited = setSearchAsyncRateLimiter.maybeExecute @@ -134,43 +132,55 @@ function App() { /> {error &&
Error: {error.message}
} -
- - - - - - - - - - - - - - - - - - - -
API calls made:{setSearchAsyncRateLimiter.state.successCount}
Rejected calls:{setSearchAsyncRateLimiter.state.rejectionCount}
Is executing: - {setSearchAsyncRateLimiter.state.isExecuting ? 'Yes' : 'No'} -
Results: - {results.length > 0 ? ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
- ) : ( - 'No results' - )} -
-
-
-        {JSON.stringify(setSearchAsyncRateLimiter.store.state, null, 2)}
-      
+ ({ + successCount: state.successCount, + rejectionCount: state.rejectionCount, + isExecuting: state.isExecuting, + })} + > + {({ successCount, rejectionCount, isExecuting }) => ( +
+ + + + + + + + + + + + + + + + + + + +
API calls made:{successCount}
Rejected calls:{rejectionCount}
Is executing:{isExecuting ? 'Yes' : 'No'}
Results: + {results.length > 0 ? ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ ) : ( + 'No results' + )} +
+
+ )} +
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useAsyncThrottler/src/index.tsx b/examples/react/useAsyncThrottler/src/index.tsx index c9dedf1e..380889e9 100644 --- a/examples/react/useAsyncThrottler/src/index.tsx +++ b/examples/react/useAsyncThrottler/src/index.tsx @@ -54,8 +54,8 @@ function App() { }, // throwOnError: true, }, - // Optional Selector function to pick the state you want to track and use - (state) => state, + // Alternative to setSearchAsyncThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) // get and name our throttled function @@ -87,24 +87,38 @@ function App() { {error &&
Error: {error.message}
} -
-

API calls made: {setSearchAsyncThrottler.state.successCount}

- {results.length > 0 && ( -
    - {results.map((item) => ( -
  • {item.title}
  • - ))} -
+ ({ + isExecuting: state.isExecuting, + isPending: state.isPending, + successCount: state.successCount, + })} + > + {({ isExecuting, isPending, successCount }) => ( +
+

API calls made: {successCount}

+ {results.length > 0 && ( +
    + {results.map((item) => ( +
  • {item.title}
  • + ))} +
+ )} + {isPending ? ( +

Pending...

+ ) : isExecuting ? ( +

Executing...

+ ) : null} +
)} - {setSearchAsyncThrottler.state.isPending ? ( -

Pending...

- ) : setSearchAsyncThrottler.state.isExecuting ? ( -

Executing...

- ) : null} -
-          {JSON.stringify(setSearchAsyncThrottler.store.state, null, 2)}
-        
-
+ + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useBatcher/src/index.tsx b/examples/react/useBatcher/src/index.tsx index 2fed18f9..b37d4689 100644 --- a/examples/react/useBatcher/src/index.tsx +++ b/examples/react/useBatcher/src/index.tsx @@ -23,61 +23,75 @@ function App1() { wait: 3000, // wait up to 3 seconds before processing a batch (if time elapses before maxSize is reached) getShouldExecute: (items, _batcher) => items.includes(42), // or pass in a custom function to determine if the batch should be processed }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - executionCount: state.executionCount, - totalItemsProcessed: state.totalItemsProcessed, - }), + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return (

TanStack Pacer useBatcher Example 1

-
Batch Size: {batcher.state.size}
-
Batch Max Size: {3}
-
Batch Items: {batcher.peekAllItems().join(', ')}
-
Batches Processed: {batcher.state.executionCount}
-
Items Processed: {batcher.state.totalItemsProcessed}
-
- Processed Batches:{' '} - {processedBatches.map((b, i) => ( + ({ + size: state.size, + executionCount: state.executionCount, + totalItemsProcessed: state.totalItemsProcessed, + })} + > + {({ size, executionCount, totalItemsProcessed }) => ( <> - [{b.join(', ')}],{' '} +
Batch Size: {size}
+
Batch Max Size: {3}
+
Batch Items: {batcher.peekAllItems().join(', ')}
+
Batches Processed: {executionCount}
+
Items Processed: {totalItemsProcessed}
+
+ Processed Batches:{' '} + {processedBatches.map((b, i) => ( + <> + [{b.join(', ')}],{' '} + + ))} +
+
+ + +
- ))} -
-
- - -
-
-        {JSON.stringify(batcher.store.state, null, 2)}
-      
+ )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useDebouncedState/src/index.tsx b/examples/react/useDebouncedState/src/index.tsx index 2faf3e72..3638ed9c 100644 --- a/examples/react/useDebouncedState/src/index.tsx +++ b/examples/react/useDebouncedState/src/index.tsx @@ -14,11 +14,8 @@ function App1() { // enabled: () => instantCount > 2, // optional, defaults to true // leading: true, // optional, defaults to false }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -35,35 +32,50 @@ function App1() {

TanStack Pacer useDebouncedState Example 1

- - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Execution Count:{debouncer.state.executionCount}
-
-
Instant Count:{instantCount}
Debounced Count:{debouncedCount}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}
+
+
Instant Count:{instantCount}
Debounced Count:{debouncedCount}
-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -78,11 +90,8 @@ function App2() { wait: 500, enabled: instantSearch.length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -106,32 +115,47 @@ function App2() { - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Execution Count:{debouncer.state.executionCount}
-
-
Instant Search:{instantSearch}
Debounced Search:{debouncedSearch}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}
+
+
Instant Search:{instantSearch}
Debounced Search:{debouncedSearch}
-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -146,11 +170,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -193,43 +214,58 @@ function App3() { - - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{debouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - debouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Debounced to 250ms wait time

-
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useDebouncedValue/src/index.tsx b/examples/react/useDebouncedValue/src/index.tsx index dfe5ddd8..2160e813 100644 --- a/examples/react/useDebouncedValue/src/index.tsx +++ b/examples/react/useDebouncedValue/src/index.tsx @@ -17,8 +17,8 @@ function App1() { // enabled: () => instantCount > 2, // optional, defaults to true // leading: true, // optional, defaults to false }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return ( @@ -54,8 +54,8 @@ function App2() { wait: 500, enabled: instantSearch.length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -101,11 +101,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -145,44 +142,57 @@ function App3() { {debouncedValue} - - - - - - - - - - - - - - - - - - - - - - - -
Is Pending:{debouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{debouncer.state.executionCount}
Saved Executions:{instantExecutionCount - debouncer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - debouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
-
-

Debounced to 250ms wait time

-
+ ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + +
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
+
+

Debounced to 250ms wait time

+
+ + )} +
-        {JSON.stringify(debouncer.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) diff --git a/examples/react/useDebouncer/src/index.tsx b/examples/react/useDebouncer/src/index.tsx index b5173af8..8c748821 100644 --- a/examples/react/useDebouncer/src/index.tsx +++ b/examples/react/useDebouncer/src/index.tsx @@ -16,11 +16,8 @@ function App1() { enabled: () => instantCount > 2, // optional, defaults to true // leading: true, // optional, defaults to false }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - status: state.status, - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -37,14 +34,25 @@ function App1() {

TanStack Pacer useDebouncer Example 1

- - - - - - - - + ({ + status: state.status, + executionCount: state.executionCount, + })} + > + {({ status, executionCount }) => ( + <> + + + + + + + + + + )} + - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
Status:{debouncer.state.status}
Execution Count:{debouncer.state.executionCount}
Status:{status}
Execution Count:{executionCount}

@@ -69,9 +77,13 @@ function App1() { Flush -
-        {JSON.stringify(debouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -87,11 +99,8 @@ function App2() { wait: 500, enabled: () => searchText.length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to setSearchDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -115,14 +124,25 @@ function App2() { - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + )} + - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
Is Pending:{setSearchDebouncer.state.isPending.toString()}
Execution Count:{setSearchDebouncer.state.executionCount}
Is Pending:{isPending.toString()}
Execution Count:{executionCount}

@@ -141,9 +161,13 @@ function App2() {
-
-        {JSON.stringify(setSearchDebouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -159,11 +183,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - isPending: state.isPending, - executionCount: state.executionCount, - }), + // Alternative to setValueDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -206,38 +227,46 @@ function App3() { - - - - - - - - - - - - - - - - - - - - + ({ + isPending: state.isPending, + executionCount: state.executionCount, + })} + > + {({ isPending, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + )} +
Is Pending:{setValueDebouncer.state.isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{setValueDebouncer.state.executionCount}
Saved Executions: - {instantExecutionCount - setValueDebouncer.state.executionCount} -
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - setValueDebouncer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Is Pending:{isPending.toString()}
Instant Executions:{instantExecutionCount}
Debounced Executions:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -246,9 +275,13 @@ function App3() {
-
-        {JSON.stringify(setValueDebouncer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useQueuedState/src/index.tsx b/examples/react/useQueuedState/src/index.tsx index 76630a98..26d3150a 100644 --- a/examples/react/useQueuedState/src/index.tsx +++ b/examples/react/useQueuedState/src/index.tsx @@ -8,6 +8,7 @@ function App1() { console.log('processing item', item) } + // Note: useQueuedState requires items in selector, but we'll use Subscribe for reactive rendering const [queueItems, addItem, queuer] = useQueuedState( processItem, { @@ -16,81 +17,94 @@ function App1() { started: false, wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - items: state.items, // required for useQueuedState - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - executionCount: state.executionCount, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return (

TanStack Pacer useQueuedState Example 1

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queueItems.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + isRunning: state.isRunning, + })} > - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + executionCount, + isRunning, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {queueItems.join(', ')}
+
+ + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -110,16 +124,8 @@ function App2() { started: true, wait: 100, }, - (state) => ({ - items: state.items, // required for useQueuedState - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - executionCount: state.executionCount, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -162,59 +168,78 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, status, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.status}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{status}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Queued with 100ms wait time

-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useQueuedValue/src/index.tsx b/examples/react/useQueuedValue/src/index.tsx index 022d3dd1..4b38799e 100644 --- a/examples/react/useQueuedValue/src/index.tsx +++ b/examples/react/useQueuedValue/src/index.tsx @@ -12,17 +12,8 @@ function App1() { maxSize: 25, wait: 500, // wait 500ms between processing value changes }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - isRunning: state.isRunning, - items: state.items, // required for useQueuedValue hook - size: state.size, - status: state.status, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return ( @@ -30,62 +21,82 @@ function App1() {

TanStack Pacer useQueuedValue Example 1

Current Value: {value}

-
Queue Size: {queuer.state.size}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.peekAllItems().join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + isRunning: state.isRunning, + })} > - { - setInstantSearchValue(e.target.value) // instantly update the local search value - }} - placeholder="Enter search term..." - disabled={queuer.state.isFull} - /> - - - - - -
+ {({ + size, + isFull, + isEmpty, + isIdle, + status, + executionCount, + isRunning, + }) => ( + <> +
Queue Size: {size}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {queuer.peekAllItems().join(', ')}
+
+ { + setInstantSearchValue(e.target.value) // instantly update the local search value + }} + placeholder="Enter search term..." + disabled={isFull} + /> + + + + + +
+ + )} +
-        {JSON.stringify(queuer.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) @@ -102,16 +113,8 @@ function App2() { maxSize: 100, wait: 100, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - isEmpty: state.isEmpty, - isFull: state.isFull, - isIdle: state.isIdle, - items: state.items, // required for useQueuedValue hook - size: state.size, - status: state.status, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -151,60 +154,77 @@ function App2() { {queuedValue} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.status}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
-
-

Queued with 100ms wait time

-
+ ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, status, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{status}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
+
+

Queued with 100ms wait time

+
+ + )} +
-        {JSON.stringify(queuer.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) diff --git a/examples/react/useQueuer/src/index.tsx b/examples/react/useQueuer/src/index.tsx index 243e5ff3..a273e23b 100644 --- a/examples/react/useQueuer/src/index.tsx +++ b/examples/react/useQueuer/src/index.tsx @@ -20,85 +20,100 @@ function App1() { started: false, // optional, defaults to true wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - status: state.status, - executionCount: state.executionCount, - items: state.items, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return (

TanStack Pacer useQueuer Example 1

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.state.items.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + status: state.status, + executionCount: state.executionCount, + items: state.items, + })} > - - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + isRunning, + status, + executionCount, + items, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {items.join(', ')}
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -120,15 +135,8 @@ function App2() { initialItems: [currentValue], wait: 100, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - executionCount: state.executionCount, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -171,51 +179,66 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, isRunning, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -224,9 +247,13 @@ function App2() {
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useQueuerWithPersister/src/index.tsx b/examples/react/useQueuerWithPersister/src/index.tsx index 54be6309..933b8e06 100644 --- a/examples/react/useQueuerWithPersister/src/index.tsx +++ b/examples/react/useQueuerWithPersister/src/index.tsx @@ -27,80 +27,104 @@ function App1() { started: false, // optional, defaults to true wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) useEffect(() => { - queuerPersister.saveState(queuer.state) - }, [queuer.state]) + queuerPersister.saveState(queuer.store.state) + }, [queuer.store.state]) return (

TanStack Pacer useQueuer Example 1 (with persister)

-
Queue Size: {queuer.state.size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state.isFull ? 'Yes' : 'No'}
-
Queue Peek: {queuer.peekNextItem()}
-
Queue Empty: {queuer.state.isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state.isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state.status}
-
Items Processed: {queuer.state.executionCount}
-
Queue Items: {queuer.state.items.join(', ')}
-
({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + status: state.status, + executionCount: state.executionCount, + items: state.items, + })} > - - - - - - - -
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ {({ + size, + isFull, + isEmpty, + isIdle, + isRunning, + status, + executionCount, + items, + }) => ( + <> +
Queue Size: {size}
+
Queue Max Size: {25}
+
Queue Full: {isFull ? 'Yes' : 'No'}
+
Queue Peek: {queuer.peekNextItem()}
+
Queue Empty: {isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {isIdle ? 'Yes' : 'No'}
+
Queuer Status: {status}
+
Items Processed: {executionCount}
+
Queue Items: {items.join(', ')}
+
+ + + + + + + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -121,15 +145,8 @@ function App2() { initialItems: [currentValue], wait: 100, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - executionCount: state.executionCount, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -172,51 +189,66 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + executionCount: state.executionCount, + })} + > + {({ size, isFull, isEmpty, isIdle, isRunning, executionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Queue Size:{queuer.state.size}
Queue Full:{queuer.state.isFull ? 'Yes' : 'No'}
Queue Empty:{queuer.state.isEmpty ? 'Yes' : 'No'}
Queue Idle:{queuer.state.isIdle ? 'Yes' : 'No'}
Queuer Status:{queuer.state.isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{queuer.state.executionCount}
Saved Executions:{instantExecutionCount - queuer.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - queuer.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Queue Size:{size}
Queue Full:{isFull ? 'Yes' : 'No'}
Queue Empty:{isEmpty ? 'Yes' : 'No'}
Queue Idle:{isIdle ? 'Yes' : 'No'}
Queuer Status:{isRunning ? 'Running' : 'Stopped'}
Instant Executions:{instantExecutionCount}
Items Processed:{executionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
@@ -225,9 +257,13 @@ function App2() {
-
-        {JSON.stringify(queuer.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useRateLimitedState/src/index.tsx b/examples/react/useRateLimitedState/src/index.tsx index e4b2b10c..f2e1e9d3 100644 --- a/examples/react/useRateLimitedState/src/index.tsx +++ b/examples/react/useRateLimitedState/src/index.tsx @@ -20,11 +20,8 @@ function App1() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -63,22 +60,33 @@ function App1() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
@@ -88,9 +96,13 @@ function App1() {
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -113,11 +125,8 @@ function App2() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -163,22 +172,33 @@ function App2() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Instant Search:{instantSearch}
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Instant Search:{instantSearch}
Rate Limited Search:{limitedSearch}
@@ -187,9 +207,13 @@ function App2() {
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -212,11 +236,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -281,52 +302,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useRateLimitedValue/src/index.tsx b/examples/react/useRateLimitedValue/src/index.tsx index 5845063d..0d430b26 100644 --- a/examples/react/useRateLimitedValue/src/index.tsx +++ b/examples/react/useRateLimitedValue/src/index.tsx @@ -21,8 +21,8 @@ function App1() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -91,8 +91,8 @@ function App2() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -168,11 +168,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -234,53 +231,65 @@ function App3() { {limitedValue} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
-
-

Rate limited to 20 updates per 2 seconds

-
+ ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +
+
+

Rate limited to 20 updates per 2 seconds

+
+ + )} +
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) diff --git a/examples/react/useRateLimiter/src/index.tsx b/examples/react/useRateLimiter/src/index.tsx index a8059d79..6fb65c85 100644 --- a/examples/react/useRateLimiter/src/index.tsx +++ b/examples/react/useRateLimiter/src/index.tsx @@ -31,11 +31,8 @@ function App1() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -74,44 +71,59 @@ function App1() { - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -133,11 +145,8 @@ function App2() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -161,42 +170,57 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Search:
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Search:
Rate Limited Search:{limitedSearch}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -218,11 +242,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -265,52 +286,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useRateLimiterWithPersister/src/index.tsx b/examples/react/useRateLimiterWithPersister/src/index.tsx index 86f0f5c8..ee8fbaae 100644 --- a/examples/react/useRateLimiterWithPersister/src/index.tsx +++ b/examples/react/useRateLimiterWithPersister/src/index.tsx @@ -34,13 +34,13 @@ function App1() { // optional local storage persister to retain state on page refresh initialState: rateLimiterPersister.loadState(), }, - // Optional Selector function to pick the state you want to track and use - (state) => state, // entire state subscription for persister - don't do this unless you need to + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) useEffect(() => { - rateLimiterPersister.saveState(rateLimiter.state) - }, [rateLimiter.state]) + rateLimiterPersister.saveState(rateLimiter.store.state) + }, [rateLimiter.store.state]) function increment() { // this pattern helps avoid common bugs with stale closures and state @@ -78,44 +78,59 @@ function App1() { - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Count:{instantCount}
Rate Limited Count:{limitedCount}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -138,11 +153,8 @@ function App2() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -166,42 +178,57 @@ function App2() { - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
-
-
Instant Search:
Rate Limited Search:{limitedSearch}
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
+
+
Instant Search:
Rate Limited Search:{limitedSearch}
-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -223,11 +250,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -270,52 +294,66 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {({ executionCount, rejectionCount }) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
Execution Count:{rateLimiter.state.executionCount}
Rejection Count:{rateLimiter.state.rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - rateLimiter.state.executionCount}
% Reduction: - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - rateLimiter.state.executionCount) / - instantExecutionCount) * - 100, - )} - % -
Execution Count:{executionCount}
Rejection Count:{rejectionCount}
Remaining in Window:{rateLimiter.getRemainingInWindow()}
Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
Instant Executions:{instantExecutionCount}
Saved Executions:{instantExecutionCount - executionCount}
% Reduction: + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100, + )} + % +

Rate limited to 20 updates per 2 seconds

-
-        {JSON.stringify(rateLimiter.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useThrottledState/src/index.tsx b/examples/react/useThrottledState/src/index.tsx index bca8b1f8..7536ffe2 100644 --- a/examples/react/useThrottledState/src/index.tsx +++ b/examples/react/useThrottledState/src/index.tsx @@ -13,10 +13,8 @@ function App1() { wait: 1000, // enabled: () => instantCount > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -33,26 +31,38 @@ function App1() {

TanStack Pacer useThrottledState Example 1

- - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{throttler.state.executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
Execution Count:{executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -67,10 +77,8 @@ function App2() { wait: 1000, // enabled: instantSearch.length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -94,23 +102,35 @@ function App2() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{throttler.state.executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
Execution Count:{executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -125,10 +145,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -171,36 +189,48 @@ function App3() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{throttler.state.executionCount}
Saved Executions: - {instantExecutionCount - throttler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - throttler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +

Throttled to 1 update per 250ms

-
-        {JSON.stringify(throttler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/useThrottledValue/src/index.tsx b/examples/react/useThrottledValue/src/index.tsx index 8768d8f0..f120fbee 100644 --- a/examples/react/useThrottledValue/src/index.tsx +++ b/examples/react/useThrottledValue/src/index.tsx @@ -17,8 +17,8 @@ function App1() { wait: 1000, // enabled: () => instantCount > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) return ( @@ -53,8 +53,8 @@ function App2() { wait: 1000, // enabled: instantSearch.length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (_state) => ({}), // No specific state access needed for this example + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -100,10 +100,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -143,37 +141,49 @@ function App3() { {throttledValue} - - - - - - - - - - - - - - - -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{throttler.state.executionCount}
Saved Executions: - {instantExecutionCount - throttler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - throttler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
-
-

Throttled to 1 update per 250ms

-
+ ({ + executionCount: state.executionCount, + })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + + +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +
+
+

Throttled to 1 update per 250ms

+
+ + )} +
-        {JSON.stringify(throttler.store.state, null, 2)}
+         state}>
+          {(state) => JSON.stringify(state, null, 2)}
+        
       
) diff --git a/examples/react/useThrottler/src/index.tsx b/examples/react/useThrottler/src/index.tsx index f381e048..29604ee1 100644 --- a/examples/react/useThrottler/src/index.tsx +++ b/examples/react/useThrottler/src/index.tsx @@ -17,8 +17,8 @@ function App1() { // trailing: true, // default // enabled: () => instantCount > 2, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ executionCount: state.executionCount }), + // Alternative to setCountThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function increment() { @@ -35,18 +35,26 @@ function App1() {

TanStack Pacer useThrottler Example 1

- - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{setCountThrottler.state.executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
Execution Count:{executionCount}
Instant Count:{instantCount}
Throttled Count:{throttledCount}
@@ -58,9 +66,13 @@ function App1() { Flush
-
-        {JSON.stringify(setCountThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -76,9 +88,8 @@ function App2() { wait: 1000, enabled: instantSearch.length > 2, }, - // Optional Selector function to pick the state you want to track and use - - (state) => ({ executionCount: state.executionCount }), + // Alternative to setSearchThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: React.ChangeEvent) { @@ -102,26 +113,38 @@ function App2() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Execution Count:{setSearchThrottler.state.executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
Execution Count:{executionCount}
Instant Search:{instantSearch}
Throttled Search:{throttledSearch}
-
-        {JSON.stringify(setSearchThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } @@ -139,8 +162,8 @@ function App3() { // leading: true, // default // trailing: true, // default }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ executionCount: state.executionCount }), + // Alternative to setValueThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -187,29 +210,36 @@ function App3() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {({ executionCount }) => ( + <> + + + + + + + + + + + + + + )} +
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{setValueThrottler.state.executionCount}
Saved Executions: - {instantExecutionCount - setValueThrottler.state.executionCount} ( - {instantExecutionCount > 0 - ? ( - ((instantExecutionCount - - setValueThrottler.state.executionCount) / - instantExecutionCount) * - 100 - ).toFixed(2) - : 0} - % Reduction in execution calls) -
Instant Execution Count:{instantExecutionCount}
Throttled Execution Count:{executionCount}
Saved Executions: + {instantExecutionCount - executionCount} ( + {instantExecutionCount > 0 + ? ( + ((instantExecutionCount - executionCount) / + instantExecutionCount) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +
@@ -218,9 +248,13 @@ function App3() {
-
-        {JSON.stringify(setValueThrottler.store.state, null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/react/util-comparison/src/index.tsx b/examples/react/util-comparison/src/index.tsx index 03c50d19..0631fc2f 100644 --- a/examples/react/util-comparison/src/index.tsx +++ b/examples/react/util-comparison/src/index.tsx @@ -3,8 +3,8 @@ import ReactDOM from 'react-dom/client' import { useDebouncer } from '@tanstack/react-pacer/debouncer' import { useThrottler } from '@tanstack/react-pacer/throttler' import { useRateLimiter } from '@tanstack/react-pacer/rate-limiter' -import { useQueuer } from '@tanstack/react-pacer/queuer' -import { useBatcher } from '@tanstack/react-pacer/batcher' +import { QueuerState, useQueuer } from '@tanstack/react-pacer/queuer' +import { BatcherState, useBatcher } from '@tanstack/react-pacer/batcher' import { pacerDevtoolsPlugin } from '@tanstack/react-pacer-devtools' import { TanStackDevtools } from '@tanstack/react-devtools' @@ -26,7 +26,8 @@ function ComparisonApp() { key: 'my-debouncer', wait: 600, }, - (state) => state, + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const throttler = useThrottler( @@ -35,7 +36,8 @@ function ComparisonApp() { key: 'my-throttler', wait: 600, }, - (state) => state, + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const rateLimiter = useRateLimiter( @@ -46,7 +48,8 @@ function ComparisonApp() { window: 2000, windowType: 'sliding', }, - (state) => state, + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const queuer = useQueuer( @@ -56,7 +59,8 @@ function ComparisonApp() { wait: 100, maxSize: 50, }, - (state) => state, + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) const batcher = useBatcher( @@ -71,7 +75,8 @@ function ComparisonApp() { wait: 600, maxSize: 5, }, - (state) => state, + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: React.ChangeEvent) { @@ -88,13 +93,18 @@ function ComparisonApp() { } // Helper function to determine sync status - function getSyncStatus(processedValue: number, utilityName: string) { + // Note: This function will be called from within Subscribe HOCs, so it receives state as parameter + function getSyncStatus( + processedValue: number, + utilityName: string, + utilityState?: any, + ) { const isOutOfSync = processedValue !== currentValue const isPending = - (utilityName === 'Debouncer' && debouncer.state.status === 'pending') || - (utilityName === 'Throttler' && throttler.state.status === 'pending') || - (utilityName === 'Queuer' && queuer.state.status === 'running') || - (utilityName === 'Batcher' && batcher.state.status === 'pending') + (utilityName === 'Debouncer' && utilityState?.status === 'pending') || + (utilityName === 'Throttler' && utilityState?.status === 'pending') || + (utilityName === 'Queuer' && utilityState?.status === 'running') || + (utilityName === 'Batcher' && utilityState?.status === 'pending') // Tooltip explanations for why certain utilities become out of sync const getTooltip = () => { @@ -126,78 +136,46 @@ function ComparisonApp() { } } - // Warning icon SVG - const WarningIcon = ({ size = 16 }: { size?: number }) => ( - - - - - - ) - - // Success icon SVG - const SuccessIcon = ({ size = 16 }: { size?: number }) => ( - - - - - ) - - const utilityData = [ + // Utility metadata (without state - state will be accessed via Subscribe HOCs) + const utilityMetadata = [ { name: 'Debouncer', value: debouncedValue, - state: debouncer.state, description: `Delays execution until after ${debouncer.options.wait}ms of inactivity`, color: '#3b82f6', // blue flush: () => debouncer.flush(), + util: debouncer, }, { name: 'Throttler', value: throttledValue, - state: throttler.state, description: `Limits execution to once every ${throttler.options.wait}ms`, color: '#0891b2', // cyan flush: () => throttler.flush(), + util: throttler, }, { name: 'Rate Limiter', value: rateLimitedValue, - state: rateLimiter.state, description: `Allows max ${rateLimiter.options.limit} executions per ${rateLimiter.options.window}ms window`, color: '#ea580c', // orange + util: rateLimiter, }, { name: 'Queuer', value: queuedValue, - state: queuer.state, description: `Processes items sequentially with ${queuer.options.wait}ms delay`, color: '#db2777', // pink flush: () => queuer.flush(), + util: queuer, }, { name: 'Batcher', value: batchedValue, - state: batcher.state, description: `Processes in batches of ${batcher.options.maxSize} or after ${batcher.options.wait}ms`, color: '#8b5cf6', // purple flush: () => batcher.flush(), + util: batcher, }, ] as const @@ -243,169 +221,181 @@ function ComparisonApp() { marginBottom: '30px', }} > - {utilityData.map((utility) => { - const syncStatus = getSyncStatus(utility.value, utility.name) - return ( -
-

- {utility.name} -

-

- {utility.description} -

- -
-
- - Value: {utility.value} - -
-
- {syncStatus.isOutOfSync ? ( - - - {syncStatus.statusText} - - ) : ( - - - {syncStatus.statusText} - - )} -
- - alert( - 'These sliders are read-only. Move the main slider at the top', - ) - } - type="range" - min="0" - max="100" - value={utility.value} - readOnly + {utilityMetadata.map((utility) => ( + state} + > + {(state) => { + const syncStatus = getSyncStatus( + utility.value, + utility.name, + state, + ) + return ( +
-
+ > +

+ {utility.name} +

+

+ {utility.description} +

-
-
- Executions: {utility.state.executionCount} -
-
- Reduction:{' '} - {instantExecutionCount === 0 - ? '0' - : Math.round( - ((instantExecutionCount - - utility.state.executionCount) / - instantExecutionCount) * - 100, +
+
+ + Value: {utility.value} + +
+
+ {syncStatus.isOutOfSync ? ( + + + {syncStatus.statusText} + + ) : ( + + + {syncStatus.statusText} + )} - % -
- {utility.name === 'Rate Limiter' && ( -
- Rejections:{' '} - {(utility.state as any).rejectionCount} +
+ + alert( + 'These sliders are read-only. Move the main slider at the top', + ) + } + type="range" + min="0" + max="100" + value={utility.value} + readOnly + style={{ + width: '100%', + margin: '2px 0', + accentColor: utility.color, + }} + />
- )} - {utility.name === 'Queuer' && ( - <> + +
- Queue Size: {utility.state.size} + Executions: {state.executionCount}
- - )} - {utility.name === 'Batcher' && ( - <>
- Batch Size: {utility.state.size} + Reduction:{' '} + {instantExecutionCount === 0 + ? '0' + : Math.round( + ((instantExecutionCount - state.executionCount) / + instantExecutionCount) * + 100, + )} + %
+ {utility.name === 'Rate Limiter' && ( +
+ Rejections:{' '} + {(state as any).rejectionCount} +
+ )} + {utility.name === 'Queuer' && ( + <> +
+ Queue Size:{' '} + {(state as QueuerState).size} +
+ + )} + {utility.name === 'Batcher' && ( + <> +
+ Batch Size:{' '} + {(state as BatcherState).size} +
+
+ Items Processed:{' '} + {(state as any).totalItemsProcessed} +
+ + )}
- Items Processed:{' '} - {(utility.state as any).totalItemsProcessed} + Status: {state.status}
- - )} -
- Status: {utility.state.status} +
+ {'flush' in utility && + typeof utility.flush === 'function' && ( + + )}
-
- {'flush' in utility && typeof utility.flush === 'function' && ( - - )} -
- ) - })} + ) + }} +
+ ))}
@@ -419,31 +409,38 @@ function ComparisonApp() { gap: '8px', }} > - {utilityData.map((utility) => ( -
-

- {utility.name} State -

-
-                {JSON.stringify(utility.state, null, 2)}
-              
-
+ {utilityMetadata.map((utility) => ( + state} + > + {(state) => ( +
+

+ {utility.name} State +

+
+                    {JSON.stringify(state, null, 2)}
+                  
+
+ )} +
))}
@@ -459,3 +456,36 @@ function ComparisonApp() { const root = ReactDOM.createRoot(document.getElementById('root')!) root.render() + +// Warning icon SVG +const WarningIcon = ({ size = 16 }: { size?: number }) => ( + + + + + +) + +// Success icon SVG +const SuccessIcon = ({ size = 16 }: { size?: number }) => ( + + + + +) diff --git a/examples/solid/createAsyncBatcher/src/index.tsx b/examples/solid/createAsyncBatcher/src/index.tsx index 01c4b412..4891e066 100644 --- a/examples/solid/createAsyncBatcher/src/index.tsx +++ b/examples/solid/createAsyncBatcher/src/index.tsx @@ -72,16 +72,8 @@ function App() { ) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - isExecuting: state.isExecuting, - status: state.status, - successCount: state.successCount, - errorCount: state.errorCount, - totalItemsProcessed: state.totalItemsProcessed, - items: state.items, - }), + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) const addItem = (isUrgent = false) => { @@ -107,73 +99,89 @@ function App() {

TanStack Pacer createAsyncBatcher Example

-
-

Batch Status

-
Current Batch Size: {batcher.state().size}
-
Max Batch Size: 5
-
Is Executing: {batcher.state().isExecuting ? 'Yes' : 'No'}
-
Status: {batcher.state().status}
-
Successful Batches: {batcher.state().successCount}
-
Failed Batches: {batcher.state().errorCount}
-
Total Items Processed: {batcher.state().totalItemsProcessed}
-
- -
-

Current Batch Items

-
- {batcher.state().items.length === 0 ? ( - No items in current batch - ) : ( - - {(item, index) => ( -
- {index() + 1}: {item.value} (added at{' '} - {new Date(item.timestamp).toLocaleTimeString()}) -
- )} -
- )} -
-
+ ({ + size: state.size, + isExecuting: state.isExecuting, + status: state.status, + successCount: state.successCount, + errorCount: state.errorCount, + totalItemsProcessed: state.totalItemsProcessed, + items: state.items, + })} + > + {(state) => ( + <> +
+

Batch Status

+
Current Batch Size: {state().size}
+
Max Batch Size: 5
+
Is Executing: {state().isExecuting ? 'Yes' : 'No'}
+
Status: {state().status}
+
Successful Batches: {state().successCount}
+
Failed Batches: {state().errorCount}
+
Total Items Processed: {state().totalItemsProcessed}
+
+ +
+

Current Batch Items

+
+ {state().items.length === 0 ? ( + No items in current batch + ) : ( + + {(item, index) => ( +
+ {index() + 1}: {item.value} (added at{' '} + {new Date(item.timestamp).toLocaleTimeString()}) +
+ )} +
+ )} +
+
+ +
+

Controls

+
+ + + + +
+
+ + )} +
-

Controls

-
- - - - -
- -
- -
+
@@ -210,6 +218,13 @@ function App() {
)} + state}> + {(state) => ( +
+            {JSON.stringify(state, null, 2)}
+          
+ )} +
) } diff --git a/examples/solid/createAsyncDebouncer/src/index.tsx b/examples/solid/createAsyncDebouncer/src/index.tsx index 6c60821d..1bcce1c8 100644 --- a/examples/solid/createAsyncDebouncer/src/index.tsx +++ b/examples/solid/createAsyncDebouncer/src/index.tsx @@ -48,13 +48,8 @@ function App() { setResults([]) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - errorCount: state.errorCount, - successCount: state.successCount, - isExecuting: state.isExecuting, - isPending: state.isPending, - }), + // Alternative to asyncDebouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) // get and name our debounced function @@ -82,21 +77,37 @@ function App() { autocomplete="new-password" /> - {asyncDebouncer.state().errorCount > 0 && ( -
Errors: {asyncDebouncer.state().errorCount}
- )} -
-

API calls made: {asyncDebouncer.state().successCount}

- {results().length > 0 && ( -
    - {(item) =>
  • {item.title}
  • }
    -
+ ({ + errorCount: state.errorCount, + successCount: state.successCount, + isExecuting: state.isExecuting, + isPending: state.isPending, + })} + > + {(state) => ( + <> + {state().errorCount > 0 &&
Errors: {state().errorCount}
} +
+

API calls made: {state().successCount}

+ {results().length > 0 && ( +
    + {(item) =>
  • {item.title}
  • }
    +
+ )} + {state().isExecuting &&

Executing...

} + {state().isPending &&

Pending...

} +
+ )} - {asyncDebouncer.state().isExecuting &&

Executing...

} - {asyncDebouncer.state().isPending &&

Pending...

} -
-
{JSON.stringify({ state: asyncDebouncer.state() }, null, 2)}
-
+ + state}> + {(state) => ( +
+            {JSON.stringify(state(), null, 2)}
+          
+ )} +
) } diff --git a/examples/solid/createAsyncQueuer/src/index.tsx b/examples/solid/createAsyncQueuer/src/index.tsx index 7311ce52..3afe8852 100644 --- a/examples/solid/createAsyncQueuer/src/index.tsx +++ b/examples/solid/createAsyncQueuer/src/index.tsx @@ -39,98 +39,113 @@ function App() { ) // optionally, handle errors here instead of your own try/catch }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - status: state.status, - successCount: state.successCount, - items: state.items, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - rejectionCount: state.rejectionCount, - activeItems: state.activeItems, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) return (

TanStack Pacer createAsyncQueuer Example

-
-
Queue Size: {queuer.state().size}
-
Queue Max Size: {25}
-
Queue Full: {queuer.state().isFull ? 'Yes' : 'No'}
-
Queue Empty: {queuer.state().isEmpty ? 'Yes' : 'No'}
-
Queue Idle: {queuer.state().isIdle ? 'Yes' : 'No'}
-
Queuer Status: {queuer.state().status}
-
Items Processed: {queuer.state().successCount}
-
Items Rejected: {queuer.state().rejectionCount}
-
Active Tasks: {queuer.state().activeItems.length}
-
Pending Tasks: {queuer.state().items.length}
-
- Concurrency:{' '} - - setConcurrency(Math.max(1, parseInt(e.currentTarget.value) || 1)) - } - style={{ width: '60px' }} - /> -
-
- Queue Items: - - {(item, index) => ( + ({ + size: state.size, + status: state.status, + successCount: state.successCount, + items: state.items, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + rejectionCount: state.rejectionCount, + activeItems: state.activeItems, + isRunning: state.isRunning, + })} + > + {(state) => ( + <> +
Queue Size: {state().size}
+
Queue Max Size: {25}
+
Queue Full: {state().isFull ? 'Yes' : 'No'}
+
Queue Empty: {state().isEmpty ? 'Yes' : 'No'}
+
Queue Idle: {state().isIdle ? 'Yes' : 'No'}
+
Queuer Status: {state().status}
+
Items Processed: {state().successCount}
+
Items Rejected: {state().rejectionCount}
+
Active Tasks: {state().activeItems.length}
+
Pending Tasks: {state().items.length}
- {index()}: {item} + Concurrency:{' '} + + setConcurrency( + Math.max(1, parseInt(e.currentTarget.value) || 1), + ) + } + style={{ width: '60px' }} + />
- )} -
-
-
- - - -
- - -
+
+ Queue Items: + + {(item, index) => ( +
+ {index()}: {item} +
+ )} +
+
+
+ + + +
+ + +
+ + )} + + state}> + {(state) => ( +
+            {JSON.stringify(state(), null, 2)}
+          
+ )} +
) } diff --git a/examples/solid/createAsyncRateLimiter/src/index.tsx b/examples/solid/createAsyncRateLimiter/src/index.tsx index 00a510ac..173a38be 100644 --- a/examples/solid/createAsyncRateLimiter/src/index.tsx +++ b/examples/solid/createAsyncRateLimiter/src/index.tsx @@ -33,8 +33,6 @@ function App() { const data = await fakeApi(term) setResults(data) - - console.log(setSearchAsyncRateLimiter.state().successCount) } // hook that gives you an async rate limiter instance @@ -56,11 +54,8 @@ function App() { ) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - successCount: state.successCount, - isExecuting: state.isExecuting, - }), + // Alternative to setSearchAsyncRateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) // get and name our rate limited function @@ -110,17 +105,36 @@ function App() { />
-

API calls made: {setSearchAsyncRateLimiter.state().successCount}

- {results().length > 0 && ( -
    - {(item) =>
  • {item.title}
  • }
    -
- )} - {setSearchAsyncRateLimiter.state().isExecuting &&

Loading...

} + ({ + successCount: state.successCount, + isExecuting: state.isExecuting, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> +

API calls made: {state().successCount}

+ {state().rejectionCount > 0 && ( +

Rate limit rejections: {state().rejectionCount}

+ )} + {results().length > 0 && ( +
    + {(item) =>
  • {item.title}
  • }
    +
+ )} + {state().isExecuting &&

Loading...

} + + )} +
-
-        {JSON.stringify(setSearchAsyncRateLimiter.state(), null, 2)}
-      
+ state}> + {(state) => ( +
+            {JSON.stringify(state(), null, 2)}
+          
+ )} +
) } diff --git a/examples/solid/createAsyncThrottler/src/index.tsx b/examples/solid/createAsyncThrottler/src/index.tsx index afe644fc..c7890b6c 100644 --- a/examples/solid/createAsyncThrottler/src/index.tsx +++ b/examples/solid/createAsyncThrottler/src/index.tsx @@ -50,12 +50,8 @@ function App() { setResults([]) }, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - successCount: state.successCount, - isPending: state.isPending, - isExecuting: state.isExecuting, - }), + // Alternative to setSearchAsyncThrottler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) // get and name our throttled function @@ -85,14 +81,33 @@ function App() { {error() &&
Error: {error()?.message}
}
-

API calls made: {setSearchAsyncThrottler.state().successCount}

- {(item) =>
  • {item.title}
  • }
    - {setSearchAsyncThrottler.state().isPending ? ( -

    Pending...

    - ) : setSearchAsyncThrottler.state().isExecuting ? ( -

    Executing...

    - ) : null} + ({ + successCount: state.successCount, + isPending: state.isPending, + isExecuting: state.isExecuting, + })} + > + {(state) => ( + <> +

    API calls made: {state().successCount}

    + {(item) =>
  • {item.title}
  • }
    + {state().isPending ? ( +

    Pending...

    + ) : state().isExecuting ? ( +

    Executing...

    + ) : null} + + )} +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createBatcher/src/index.tsx b/examples/solid/createBatcher/src/index.tsx index c433002d..a2f3b36c 100644 --- a/examples/solid/createBatcher/src/index.tsx +++ b/examples/solid/createBatcher/src/index.tsx @@ -18,60 +18,72 @@ function App1() { wait: 3000, getShouldExecute: (items) => items.includes(42), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - items: state.items, - executionCount: state.executionCount, - totalItemsProcessed: state.totalItemsProcessed, - }), + // Alternative to batcher.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) return (

    TanStack Pacer createBatcher Example 1

    -
    Batch Size: {batcher.state().size}
    -
    Batch Max Size: {5}
    -
    Batch Items: {batcher.state().items.join(', ')}
    -
    Batches Processed: {batcher.state().executionCount}
    -
    Items Processed: {batcher.state().totalItemsProcessed}
    -
    - Processed Batches:{' '} - - {(b) => [{b.join(', ')}], } - -
    -
    ({ + size: state.size, + items: state.items, + executionCount: state.executionCount, + totalItemsProcessed: state.totalItemsProcessed, + })} > - - -
    -
    -        {JSON.stringify(batcher.state(), null, 2)}
    -      
    + {(state) => ( + <> +
    Batch Size: {state().size}
    +
    Batch Max Size: {5}
    +
    Batch Items: {state().items.join(', ')}
    +
    Batches Processed: {state().executionCount}
    +
    Items Processed: {state().totalItemsProcessed}
    +
    + Processed Batches:{' '} + + {(b) => [{b.join(', ')}], } + +
    +
    + + +
    + + )} + + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createDebouncedSignal/src/index.tsx b/examples/solid/createDebouncedSignal/src/index.tsx index 1d553cb9..74433890 100644 --- a/examples/solid/createDebouncedSignal/src/index.tsx +++ b/examples/solid/createDebouncedSignal/src/index.tsx @@ -14,10 +14,10 @@ function App1() { // enabled: () => instantCount() > 2, // optional, defaults to true // leading: true, // optional, defaults to false }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function increment() { @@ -34,10 +34,18 @@ function App1() {

    TanStack Pacer createDebouncedSignal Example 1

    - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + + + + + )} + - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{debouncer.state().executionCount}
    Execution Count:{state().executionCount}

    @@ -70,10 +78,10 @@ function App2() { { wait: 500, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleSearchChange(e: Event) { @@ -98,10 +106,18 @@ function App2() { - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + + + + + )} + - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{debouncer.state().executionCount}
    Execution Count:{state().executionCount}

    @@ -131,10 +147,10 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleRangeChange(e: Event) { @@ -182,30 +198,37 @@ function App3() {
    Instant Executions: {instantExecutionCount()}
    Debounced Executions:{debouncer.state().executionCount}
    Saved Executions: - {instantExecutionCount() - debouncer.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - debouncer.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Debounced Executions:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    diff --git a/examples/solid/createDebouncedValue/src/index.tsx b/examples/solid/createDebouncedValue/src/index.tsx index 165c7e05..62bffee8 100644 --- a/examples/solid/createDebouncedValue/src/index.tsx +++ b/examples/solid/createDebouncedValue/src/index.tsx @@ -90,10 +90,10 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to debouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleRangeChange(e: Event) { @@ -140,30 +140,37 @@ function App3() {
    Instant Executions: {instantExecutionCount()}
    Debounced Executions:{debouncer.state().executionCount}
    Saved Executions: - {instantExecutionCount() - debouncer.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - debouncer.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Debounced Executions:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    diff --git a/examples/solid/createDebouncer/src/index.tsx b/examples/solid/createDebouncer/src/index.tsx index bbee6b11..303911b1 100644 --- a/examples/solid/createDebouncer/src/index.tsx +++ b/examples/solid/createDebouncer/src/index.tsx @@ -11,14 +11,12 @@ function App1() { const setCountDebouncer = createDebouncer( setDebouncedCount, { - wait: 500, - // enabled: () => instantCount() > 2, // optional, defaults to true + wait: 800, + enabled: () => instantCount() > 2, // optional, defaults to true // leading: true, // optional, defaults to false }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setCountDebouncer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function increment() { @@ -35,28 +33,56 @@ function App1() {

    TanStack Pacer createDebouncer Example 1

    - - - - - - - - - - - - - - - + ({ + status: state.status, + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
    Execution Count:{setCountDebouncer.state().executionCount}
    -
    -
    Instant Count:{instantCount()}
    Debounced Count:{debouncedCount()}
    Status:{state().status}
    Execution Count:{state().executionCount}
    +
    +
    Instant Count:{instantCount()}
    Debounced Count:{debouncedCount()}
    +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } @@ -70,12 +96,10 @@ function App2() { setDebouncedSearchText, { wait: 500, - enabled: () => searchText().length > 2, + enabled: () => searchText().length > 2, // optional, defaults to true }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setSearchDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: Event) { @@ -100,25 +124,50 @@ function App2() { - - - - - - - - - - - - - - - + ({ + status: state.status, + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + )} +
    Execution Count:{setSearchDebouncer.state().executionCount}
    -
    -
    Instant Search:{searchText()}
    Debounced Search:{debouncedSearchText()}
    Status:{state().status}
    Execution Count:{state().executionCount}
    +
    +
    Instant Search:{searchText()}
    Debounced Search:{debouncedSearchText()}
    +
    + +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } @@ -134,10 +183,8 @@ function App3() { { wait: 250, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setValueDebouncer.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: Event) { @@ -181,40 +228,58 @@ function App3() { - - - - - - - - - - - - - - - - + ({ + status: state.status, + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + )} +
    Instant Executions:{instantExecutionCount()}
    Debounced Executions:{setValueDebouncer.state().executionCount}
    Saved Executions: - {instantExecutionCount() - - setValueDebouncer.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - setValueDebouncer.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Status:{state().status}
    Instant Executions:{instantExecutionCount()}
    Debounced Executions:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +

    Debounced with 250ms wait time

    + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createQueuedSignal/src/index.tsx b/examples/solid/createQueuedSignal/src/index.tsx index 0eb4323c..b037ac3f 100644 --- a/examples/solid/createQueuedSignal/src/index.tsx +++ b/examples/solid/createQueuedSignal/src/index.tsx @@ -16,30 +16,46 @@ function App1() { started: false, wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - items: state.items, // required for createQueuedSignal - size: state.size, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - status: state.status, - executionCount: state.executionCount, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // items: state.items, // required for createQueuedSignal + // size: state.size, + // isFull: state.isFull, + // isEmpty: state.isEmpty, + // isIdle: state.isIdle, + // status: state.status, + // executionCount: state.executionCount, + // isRunning: state.isRunning, + // }), ) return (

    TanStack Pacer createQueuedSignal Example 1

    -
    Queue Size: {queuer.state().size}
    -
    Queue Max Size: {25}
    -
    Queue Full: {queuer.state().isFull ? 'Yes' : 'No'}
    -
    Queue Peek: {queuer.peekNextItem()}
    -
    Queue Empty: {queuer.state().isEmpty ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.state().isIdle ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.state().status}
    -
    Items Processed: {queuer.state().executionCount}
    + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + isRunning: state.isRunning, + })} + > + {(state) => ( + <> +
    Queue Size: {state().size}
    +
    Queue Max Size: {25}
    +
    Queue Full: {state().isFull ? 'Yes' : 'No'}
    +
    Queue Peek: {queuer.peekNextItem()}
    +
    Queue Empty: {state().isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {state().isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {state().status}
    +
    Items Processed: {state().executionCount}
    + + )} +
    Queue Items: {queueItems().join(', ')}
    - - - - - - + {(state) => ( + <> + + + + + + + + )} +
             {JSON.stringify(queuer.store.state, null, 2)}
    @@ -115,16 +137,17 @@ function App2() {
           started: true,
           wait: 100,
         },
    -    (state) => ({
    -      items: state.items, // required for createQueuedSignal
    -      size: state.size,
    -      isFull: state.isFull,
    -      isEmpty: state.isEmpty,
    -      isIdle: state.isIdle,
    -      status: state.status,
    -      executionCount: state.executionCount,
    -      isRunning: state.isRunning,
    -    }),
    +    // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates
    +    // (state) => ({
    +    //   items: state.items, // required for createQueuedSignal
    +    //   size: state.size,
    +    //   isFull: state.isFull,
    +    //   isEmpty: state.isEmpty,
    +    //   isIdle: state.isIdle,
    +    //   status: state.status,
    +    //   executionCount: state.executionCount,
    +    //   isRunning: state.isRunning,
    +    // }),
       )
     
       function handleRangeChange(e: Event) {
    @@ -154,51 +177,66 @@ function App2() {
           
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + status: state.status, + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
    Queue Size:{queuer.state().size}
    Queue Full:{queuer.state().isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.state().isEmpty ? 'Yes' : 'No'}
    Queue Idle:{queuer.state().isIdle ? 'Yes' : 'No'}
    Queuer Status:{queuer.state().status}
    Instant Executions: {instantExecutionCount()}
    Items Processed:{queuer.state().executionCount}
    Saved Executions:{instantExecutionCount() - queuer.state().executionCount}
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - queuer.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Queue Size:{state().size}
    Queue Full:{state().isFull ? 'Yes' : 'No'}
    Queue Empty:{state().isEmpty ? 'Yes' : 'No'}
    Queue Idle:{state().isIdle ? 'Yes' : 'No'}
    Queuer Status:{state().status}
    Items Processed:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    diff --git a/examples/solid/createQueuer/src/index.tsx b/examples/solid/createQueuer/src/index.tsx index 4bb58ce8..1a9a8309 100644 --- a/examples/solid/createQueuer/src/index.tsx +++ b/examples/solid/createQueuer/src/index.tsx @@ -15,85 +15,94 @@ function App1() { started: false, wait: 1000, // wait 1 second between processing items - wait is optional! }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - status: state.status, - executionCount: state.executionCount, - items: state.items, - isFull: state.isFull, - isEmpty: state.isEmpty, - isIdle: state.isIdle, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) return (

    TanStack Pacer createQueuer Example 1

    -
    Queue Size: {queuer.state().size}
    -
    Queue Max Size: {25}
    -
    Queue Full: {queuer.state().isFull ? 'Yes' : 'No'}
    -
    Queue Peek: {queuer.peekNextItem()}
    -
    Queue Empty: {queuer.state().isEmpty ? 'Yes' : 'No'}
    -
    Queue Idle: {queuer.state().isIdle ? 'Yes' : 'No'}
    -
    Queuer Status: {queuer.state().status}
    -
    Items Processed: {queuer.state().executionCount}
    -
    Queue Items: {queuer.state().items.join(', ')}
    -
    ({ + size: state.size, + status: state.status, + executionCount: state.executionCount, + items: state.items, + isFull: state.isFull, + isEmpty: state.isEmpty, + isIdle: state.isIdle, + isRunning: state.isRunning, + })} > - - - - - - -
    + {(state) => ( + <> +
    Queue Size: {state().size}
    +
    Queue Max Size: {25}
    +
    Queue Full: {state().isFull ? 'Yes' : 'No'}
    +
    Queue Peek: {queuer.peekNextItem()}
    +
    Queue Empty: {state().isEmpty ? 'Yes' : 'No'}
    +
    Queue Idle: {state().isIdle ? 'Yes' : 'No'}
    +
    Queuer Status: {state().status}
    +
    Items Processed: {state().executionCount}
    +
    Queue Items: {state().items.join(', ')}
    +
    + + + + + + +
    + + )} + + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } @@ -110,14 +119,8 @@ function App2() { maxSize: 100, wait: 500, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - executionCount: state.executionCount, - items: state.items, - isEmpty: state.isEmpty, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function handleInputChange(e: Event) { @@ -142,58 +145,74 @@ function App2() {
    - - - - - - - - - - - - - - - - + ({ + size: state.size, + executionCount: state.executionCount, + items: state.items, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + )} +
    Queued Text:{queuedText()}
    Queue Size:{queuer.state().size}
    Items Processed:{queuer.state().executionCount}
    Queue Items:{queuer.state().items.join(', ')}
    Queued Text:{queuedText()}
    Queue Size:{state().size}
    Items Processed:{state().executionCount}
    Queue Items:{state().items.join(', ')}
    -
    ({ + isEmpty: state.isEmpty, + isRunning: state.isRunning, + })} > - - - - -
    + {(state) => ( +
    + + + + +
    + )} + + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } @@ -211,15 +230,8 @@ function App3() { maxSize: 100, wait: 100, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - size: state.size, - executionCount: state.executionCount, - items: state.items, - isFull: state.isFull, - isEmpty: state.isEmpty, - isRunning: state.isRunning, - }), + // Alternative to queuer.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function handleRangeChange(e: Event) { @@ -263,79 +275,97 @@ function App3() { - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ({ + size: state.size, + executionCount: state.executionCount, + items: state.items, + isFull: state.isFull, + isEmpty: state.isEmpty, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
    Instant Executions:{instantExecutionCount()}
    Queue Size:{queuer.state().size}
    Queue Full:{queuer.state().isFull ? 'Yes' : 'No'}
    Queue Empty:{queuer.state().isEmpty ? 'Yes' : 'No'}
    Items Processed:{queuer.state().executionCount}
    % Saved: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - queuer.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Queue Items:{queuer.state().items.join(', ')}
    Instant Executions:{instantExecutionCount()}
    Queue Size:{state().size}
    Queue Full:{state().isFull ? 'Yes' : 'No'}
    Queue Empty:{state().isEmpty ? 'Yes' : 'No'}
    Items Processed:{state().executionCount}
    % Saved: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    Queue Items:{state().items.join(', ')}
    -
    ({ + isEmpty: state.isEmpty, + isRunning: state.isRunning, + })} > - - - - -
    + {(state) => ( +
    + + + + +
    + )} + + state}> + {(state) => ( +
    +            {JSON.stringify(state(), null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createRateLimitedSignal/src/index.tsx b/examples/solid/createRateLimitedSignal/src/index.tsx index 16641ef1..63c54478 100644 --- a/examples/solid/createRateLimitedSignal/src/index.tsx +++ b/examples/solid/createRateLimitedSignal/src/index.tsx @@ -14,10 +14,11 @@ function App1() { window: 5000, windowType: windowType(), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function increment() { @@ -56,14 +57,25 @@ function App1() { - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + )} + @@ -81,9 +93,13 @@ function App1() { -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -101,10 +117,11 @@ function App2() { window: 5000, windowType: windowType(), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function handleSearchChange(e: Event) { @@ -151,14 +168,25 @@ function App2() {
    Execution Count:{rateLimiter.state().executionCount}
    Rejection Count:{rateLimiter.state().rejectionCount}
    Execution Count:{state().executionCount}
    Rejection Count:{state().rejectionCount}
    Instant Count: {instantCount()}
    - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + )} + @@ -175,9 +203,13 @@ function App2() { -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -200,10 +232,11 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function handleRangeChange(e: Event) { @@ -273,36 +306,50 @@ function App3() { - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{rateLimiter.state().executionCount}
    Rejection Count:{rateLimiter.state().rejectionCount}
    Execution Count:{state().executionCount}
    Rejection Count:{state().rejectionCount}
    Instant Search: {instantSearch()} Instant Executions: {instantExecutionCount()}
    Rate Limited Executions:{rateLimiter.state().executionCount}
    Rejected Executions:{rateLimiter.state().rejectionCount}
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - rateLimiter.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Rate Limited Executions:{state().executionCount}
    Rejected Executions:{state().rejectionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +

    Rate limited to 20 updates per 2 seconds

    -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createRateLimitedValue/src/index.tsx b/examples/solid/createRateLimitedValue/src/index.tsx index 3372f188..84e075c4 100644 --- a/examples/solid/createRateLimitedValue/src/index.tsx +++ b/examples/solid/createRateLimitedValue/src/index.tsx @@ -14,10 +14,11 @@ function App1() { window: 5000, windowType: windowType(), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function increment() { @@ -80,10 +81,11 @@ function App2() { window: 5000, windowType: windowType(), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function handleSearchChange(e: Event) { @@ -159,10 +161,11 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // rejectionCount: state.rejectionCount, + // }), ) function handleRangeChange(e: Event) { @@ -231,36 +234,50 @@ function App3() {
    Instant Executions: {instantExecutionCount()}
    Rate Limited Executions:{rateLimiter.state().executionCount}
    Rejected Executions:{rateLimiter.state().rejectionCount}
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - rateLimiter.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Rate Limited Executions:{state().executionCount}
    Rejected Executions:{state().rejectionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +

    Rate limited to 20 updates per 2 seconds

    -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createRateLimiter/src/index.tsx b/examples/solid/createRateLimiter/src/index.tsx index fe8d8612..a0922bd2 100644 --- a/examples/solid/createRateLimiter/src/index.tsx +++ b/examples/solid/createRateLimiter/src/index.tsx @@ -15,12 +15,14 @@ function App1() { limit: 5, window: 5000, windowType: windowType(), + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function increment() { @@ -59,34 +61,59 @@ function App1() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
    Execution Count:{rateLimiter.state().executionCount}
    Rejection Count:{rateLimiter.state().rejectionCount}
    Instant Count:{instantCount()}
    Rate Limited Count:{limitedCount()}
    Execution Count:{state().executionCount}
    Rejection Count:{state().rejectionCount}
    Remaining in Window:{rateLimiter.getRemainingInWindow()}
    Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
    +
    +
    Instant Count:{instantCount()}
    Rate Limited Count:{limitedCount()}
    - - +
    -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -100,15 +127,18 @@ function App2() { const rateLimiter = createRateLimiter( setLimitedSearch, { + enabled: instantSearch().length > 2, // optional, defaults to true limit: 5, window: 5000, windowType: windowType(), + onReject: (rateLimiter) => + console.log( + 'Rejected by rate limiter', + rateLimiter.getMsUntilNextWindow(), + ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function handleSearchChange(e: Event) { @@ -155,33 +185,57 @@ function App2() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
    Execution Count:{rateLimiter.state().executionCount}
    Rejection Count:{rateLimiter.state().rejectionCount}
    Instant Search:{instantSearch()}
    Rate Limited Search:{limitedSearch()}
    Execution Count:{state().executionCount}
    Rejection Count:{state().rejectionCount}
    Remaining in Window:{rateLimiter.getRemainingInWindow()}
    Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
    +
    +
    Instant Search:
    Rate Limited Search:{limitedSearch()}
    - - +
    -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -205,11 +259,8 @@ function App3() { rateLimiter.getMsUntilNextWindow(), ), }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - rejectionCount: state.rejectionCount, - }), + // Alternative to rateLimiter.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function handleRangeChange(e: Event) { @@ -275,40 +326,66 @@ function App3() { - - - - - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + rejectionCount: state.rejectionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
    Instant Executions:{instantExecutionCount()}
    Rate Limited Executions:{rateLimiter.state().executionCount}
    Rejected Executions:{rateLimiter.state().rejectionCount}
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - rateLimiter.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Execution Count:{state().executionCount}
    Rejection Count:{state().rejectionCount}
    Remaining in Window:{rateLimiter.getRemainingInWindow()}
    Ms Until Next Window:{rateLimiter.getMsUntilNextWindow()}
    Instant Executions:{instantExecutionCount()}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +

    Rate limited to 20 updates per 2 seconds

    -
    -        {JSON.stringify(rateLimiter.state(), null, 2)}
    -      
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } diff --git a/examples/solid/createThrottledSignal/src/index.tsx b/examples/solid/createThrottledSignal/src/index.tsx index 64904eb5..62476daf 100644 --- a/examples/solid/createThrottledSignal/src/index.tsx +++ b/examples/solid/createThrottledSignal/src/index.tsx @@ -13,9 +13,10 @@ function App1() { wait: 1000, // enabled: () => instantCount() > 2, // optional, defaults to true }, - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function increment() { @@ -32,10 +33,18 @@ function App1() {

    TanStack Pacer createThrottledSignal Example 1

    - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + + + + + )} + @@ -64,9 +73,10 @@ function App2() { wait: 1000, // enabled: () => instantSearch().length > 2, // optional, defaults to true }, - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleSearchChange(e: Event) { @@ -91,10 +101,18 @@ function App2() {
    Execution Count:{throttler.state().executionCount}
    Execution Count:{state().executionCount}
    Instant Count: {instantCount()}
    - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + + + + + )} + @@ -119,9 +137,10 @@ function App3() { { wait: 250, }, - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleRangeChange(e: Event) { @@ -169,30 +188,37 @@ function App3() { - - - - - - - - - - - - + ({ + executionCount: state.executionCount, + })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{throttler.state().executionCount}
    Execution Count:{state().executionCount}
    Instant Search: {instantSearch()}Instant Executions: {instantExecutionCount()}
    Throttled Executions:{throttler.state().executionCount}
    Saved Executions: - {instantExecutionCount() - throttler.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - throttler.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Throttled Executions:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    diff --git a/examples/solid/createThrottledValue/src/index.tsx b/examples/solid/createThrottledValue/src/index.tsx index fab3ce84..4b053caa 100644 --- a/examples/solid/createThrottledValue/src/index.tsx +++ b/examples/solid/createThrottledValue/src/index.tsx @@ -16,9 +16,10 @@ function App1() { { wait: 1000, }, - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) return ( @@ -94,9 +95,10 @@ function App3() { { wait: 250, }, - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to throttler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => ({ + // executionCount: state.executionCount, + // }), ) function handleRangeChange(e: Event) { @@ -143,30 +145,37 @@ function App3() {
    Instant Executions: {instantExecutionCount()}
    Throttled Executions:{throttler.state().executionCount}
    Saved Executions: - {instantExecutionCount() - throttler.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - throttler.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Throttled Executions:{state().executionCount}
    Saved Executions:{instantExecutionCount() - state().executionCount}
    % Reduction: + {instantExecutionCount() === 0 + ? '0' + : Math.round( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100, + )} + % +
    diff --git a/examples/solid/createThrottler/src/index.tsx b/examples/solid/createThrottler/src/index.tsx index 8b389e5d..953b9825 100644 --- a/examples/solid/createThrottler/src/index.tsx +++ b/examples/solid/createThrottler/src/index.tsx @@ -12,12 +12,12 @@ function App1() { setThrottledCount, { wait: 1000, - enabled: () => instantCount() > 2, // optional, defaults to true + // leading: true, // default + // trailing: true, // default + // enabled: () => instantCount() > 2, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setCountThrottler.Subscribe: pass a selector as 3rd arg to track state and subscribe to updates + // (state) => state, ) function increment() { @@ -34,23 +34,44 @@ function App1() {

    TanStack Pacer createThrottler Example 1

    - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{setCountThrottler.state().executionCount}
    Instant Count:{instantCount()}
    Throttled Count:{throttledCount()}
    Execution Count:{state().executionCount}
    Instant Count:{instantCount()}
    Throttled Count:{throttledCount()}
    +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -64,12 +85,10 @@ function App2() { setThrottledSearch, { wait: 1000, - // enabled: () => instantSearch().length > 2, // optional, defaults to true + enabled: instantSearch().length > 2, }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setSearchThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleSearchChange(e: Event) { @@ -94,20 +113,38 @@ function App2() { - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Execution Count:{setSearchThrottler.state().executionCount}
    Instant Search:{instantSearch()}
    Throttled Search:{throttledSearch()}
    Execution Count:{state().executionCount}
    Instant Search:{instantSearch()}
    Throttled Search:{throttledSearch()}
    +
    + +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } @@ -122,11 +159,11 @@ function App3() { setThrottledValue, { wait: 250, + // leading: true, // default + // trailing: true, // default }, - // Optional Selector function to pick the state you want to track and use - (state) => ({ - executionCount: state.executionCount, - }), + // Alternative to setValueThrottler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state + // (state) => state, ) function handleRangeChange(e: Event) { @@ -170,40 +207,51 @@ function App3() { - - - - - - - - - - - - - - - - + ({ executionCount: state.executionCount })} + > + {(state) => ( + <> + + + + + + + + + + + + + + )} +
    Instant Executions:{instantExecutionCount()}
    Throttled Executions:{setValueThrottler.state().executionCount}
    Saved Executions: - {instantExecutionCount() - - setValueThrottler.state().executionCount} -
    % Reduction: - {instantExecutionCount() === 0 - ? '0' - : Math.round( - ((instantExecutionCount() - - setValueThrottler.state().executionCount) / - instantExecutionCount()) * - 100, - )} - % -
    Instant Execution Count:{instantExecutionCount()}
    Throttled Execution Count:{state().executionCount}
    Saved Executions: + {instantExecutionCount() - state().executionCount} ( + {instantExecutionCount() > 0 + ? ( + ((instantExecutionCount() - state().executionCount) / + instantExecutionCount()) * + 100 + ).toFixed(2) + : 0} + % Reduction in execution calls) +
    -

    Throttled with 250ms wait time

    +

    Throttled to 1 update per 250ms (trailing edge)

    +
    +
    +
    + state}> + {(state) => ( +
    +            {JSON.stringify(state, null, 2)}
    +          
    + )} +
    ) } diff --git a/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts b/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts index a9d76ec8..f34069b0 100644 --- a/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts +++ b/packages/preact-pacer/src/async-batcher/useAsyncBatcher.ts @@ -7,11 +7,29 @@ import type { AsyncBatcherOptions, AsyncBatcherState, } from '@tanstack/pacer/async-batcher' +import type { ComponentChildren } from 'preact' -export interface ReactAsyncBatcher extends Omit< +export interface PreactAsyncBatcher extends Omit< AsyncBatcher, 'store' > { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the async batcher state. + * + * This is useful for opting into state re-renders for specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size })}> + * {({ size }) => ( + *
    Batch Size: {size}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the batcher state changes * @@ -57,14 +75,24 @@ export interface ReactAsyncBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of batch executions that have resulted in errors @@ -93,7 +121,14 @@ export interface ReactAsyncBatcher extends Omit< * { maxSize: 10, wait: 2000 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size })}> + * {({ size }) => ( + *
    Batch Size: {size}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncBatcher = useAsyncBatcher( * async (items) => { * const results = await Promise.all(items.map(item => processItem(item))); @@ -172,15 +207,31 @@ export function useAsyncBatcher( options: AsyncBatcherOptions = {}, selector: (state: AsyncBatcherState) => TSelected = () => ({}) as TSelected, -): ReactAsyncBatcher { +): PreactAsyncBatcher { const mergedOptions = { ...useDefaultPacerOptions().asyncBatcher, ...options, } as AsyncBatcherOptions - const [asyncBatcher] = useState( - () => new AsyncBatcher(fn, mergedOptions), - ) + const [asyncBatcher] = useState(() => { + const batcherInstance = new AsyncBatcher( + fn, + mergedOptions, + ) as unknown as PreactAsyncBatcher + + batcherInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(batcherInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return batcherInstance + }) asyncBatcher.fn = fn asyncBatcher.setOptions(mergedOptions) @@ -192,7 +243,7 @@ export function useAsyncBatcher( ({ ...asyncBatcher, state, - }) as ReactAsyncBatcher, // omit `store` in favor of `state` + }) as PreactAsyncBatcher, // omit `store` in favor of `state` [asyncBatcher, state], ) } diff --git a/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts index 963d177b..d5a73739 100644 --- a/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/preact-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -8,11 +8,29 @@ import type { AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer/async-debouncer' +import type { ComponentChildren } from 'preact' -export interface ReactAsyncDebouncer< +export interface PreactAsyncDebouncer< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the async debouncer state. + * + * This is useful for opting into state re-renders for specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the debouncer state changes * @@ -53,14 +71,24 @@ export interface ReactAsyncDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -84,7 +112,14 @@ export interface ReactAsyncDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Searching...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const searchDebouncer = useAsyncDebouncer( * async (query: string) => { * const results = await api.search(query); @@ -152,15 +187,31 @@ export function useAsyncDebouncer( options: AsyncDebouncerOptions, selector: (state: AsyncDebouncerState) => TSelected = () => ({}) as TSelected, -): ReactAsyncDebouncer { +): PreactAsyncDebouncer { const mergedOptions = { ...useDefaultPacerOptions().asyncDebouncer, ...options, } as AsyncDebouncerOptions - const [asyncDebouncer] = useState( - () => new AsyncDebouncer(fn, mergedOptions), - ) + const [asyncDebouncer] = useState(() => { + const debouncerInstance = new AsyncDebouncer( + fn, + mergedOptions, + ) as unknown as PreactAsyncDebouncer + + debouncerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(debouncerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return debouncerInstance + }) asyncDebouncer.fn = fn asyncDebouncer.setOptions(mergedOptions) @@ -178,7 +229,7 @@ export function useAsyncDebouncer( ({ ...asyncDebouncer, state, - }) as ReactAsyncDebouncer, // omit `store` in favor of `state` + }) as PreactAsyncDebouncer, // omit `store` in favor of `state` [asyncDebouncer, state], ) } diff --git a/packages/preact-pacer/src/async-queuer/useAsyncQueuedState.ts b/packages/preact-pacer/src/async-queuer/useAsyncQueuedState.ts index 2a6574d0..8a23b42f 100644 --- a/packages/preact-pacer/src/async-queuer/useAsyncQueuedState.ts +++ b/packages/preact-pacer/src/async-queuer/useAsyncQueuedState.ts @@ -1,5 +1,5 @@ import { useAsyncQueuer } from './useAsyncQueuer' -import type { ReactAsyncQueuer } from './useAsyncQueuer' +import type { PreactAsyncQueuer } from './useAsyncQueuer' import type { AsyncQueuerOptions, AsyncQueuerState, @@ -158,7 +158,7 @@ export function useAsyncQueuedState< fn: (value: TValue) => Promise, options: AsyncQueuerOptions = {}, selector?: (state: AsyncQueuerState) => TSelected, -): [Array, ReactAsyncQueuer] { +): [Array, PreactAsyncQueuer] { const asyncQueuer = useAsyncQueuer(fn, options, selector) return [asyncQueuer.state.items, asyncQueuer] diff --git a/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts index 6008d816..a0df7c21 100644 --- a/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/preact-pacer/src/async-queuer/useAsyncQueuer.ts @@ -7,11 +7,29 @@ import type { AsyncQueuerOptions, AsyncQueuerState, } from '@tanstack/pacer/async-queuer' +import type { ComponentChildren } from 'preact' -export interface ReactAsyncQueuer extends Omit< +export interface PreactAsyncQueuer extends Omit< AsyncQueuer, 'store' > { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the async queuer state. + * + * This is useful for opting into state re-renders for specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size })}> + * {({ size }) => ( + *
    Queue Size: {size}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the queuer state changes * @@ -50,14 +68,24 @@ export interface ReactAsyncQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `activeItems`: Items currently being processed by the queuer @@ -88,7 +116,14 @@ export interface ReactAsyncQueuer extends Omit< * { concurrency: 2, maxSize: 100, started: false } * ); * - * // Opt-in to re-render when queue size changes (optimized for displaying queue length) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size })}> + * {({ size }) => ( + *
    Queue Size: {size}
    + * )} + *
    + * + * // Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) * const asyncQueuer = useAsyncQueuer( * async (item) => { * const result = await processItem(item); @@ -172,15 +207,31 @@ export function useAsyncQueuer( options: AsyncQueuerOptions = {}, selector: (state: AsyncQueuerState) => TSelected = () => ({}) as TSelected, -): ReactAsyncQueuer { +): PreactAsyncQueuer { const mergedOptions = { ...useDefaultPacerOptions().asyncQueuer, ...options, } as AsyncQueuerOptions - const [asyncQueuer] = useState( - () => new AsyncQueuer(fn, mergedOptions), - ) + const [asyncQueuer] = useState(() => { + const queuerInstance = new AsyncQueuer( + fn, + mergedOptions, + ) as unknown as PreactAsyncQueuer + + queuerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(queuerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return queuerInstance + }) asyncQueuer.fn = fn asyncQueuer.setOptions(mergedOptions) @@ -192,7 +243,7 @@ export function useAsyncQueuer( ({ ...asyncQueuer, state, - }) as ReactAsyncQueuer, // omit `store` in favor of `state` + }) as PreactAsyncQueuer, // omit `store` in favor of `state` [asyncQueuer, state], ) } diff --git a/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index 72204962..37b0dbc8 100644 --- a/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/preact-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -8,11 +8,29 @@ import type { AsyncRateLimiterOptions, AsyncRateLimiterState, } from '@tanstack/pacer/async-rate-limiter' +import type { ComponentChildren } from 'preact' -export interface ReactAsyncRateLimiter< +export interface PreactAsyncRateLimiter< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the async rate limiter state. + * + * This is useful for opting into state re-renders for specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejections: {rejectionCount}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the rate limiter state changes * @@ -57,14 +75,24 @@ export interface ReactAsyncRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of function executions that have resulted in errors @@ -86,7 +114,14 @@ export interface ReactAsyncRateLimiter< * { limit: 5, window: 1000 } // 5 calls per second * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejections: {rejectionCount}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncRateLimiter = useAsyncRateLimiter( * async (id: string) => { * const data = await api.fetchData(id); @@ -184,15 +219,31 @@ export function useAsyncRateLimiter< options: AsyncRateLimiterOptions, selector: (state: AsyncRateLimiterState) => TSelected = () => ({}) as TSelected, -): ReactAsyncRateLimiter { +): PreactAsyncRateLimiter { const mergedOptions = { ...useDefaultPacerOptions().asyncRateLimiter, ...options, } as AsyncRateLimiterOptions - const [asyncRateLimiter] = useState( - () => new AsyncRateLimiter(fn, mergedOptions), - ) + const [asyncRateLimiter] = useState(() => { + const rateLimiterInstance = new AsyncRateLimiter( + fn, + mergedOptions, + ) as unknown as PreactAsyncRateLimiter + + rateLimiterInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(rateLimiterInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return rateLimiterInstance + }) asyncRateLimiter.fn = fn asyncRateLimiter.setOptions(mergedOptions) @@ -204,7 +255,7 @@ export function useAsyncRateLimiter< ({ ...asyncRateLimiter, state, - }) as ReactAsyncRateLimiter, // omit `store` in favor of `state` + }) as PreactAsyncRateLimiter, // omit `store` in favor of `state` [asyncRateLimiter, state], ) } diff --git a/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts b/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts index cfe928ae..9770d39c 100644 --- a/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts +++ b/packages/preact-pacer/src/async-throttler/useAsyncThrottler.ts @@ -8,11 +8,29 @@ import type { AsyncThrottlerOptions, AsyncThrottlerState, } from '@tanstack/pacer/async-throttler' +import type { ComponentChildren } from 'preact' -export interface ReactAsyncThrottler< +export interface PreactAsyncThrottler< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the async throttler state. + * + * This is useful for opting into state re-renders for specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the throttler state changes * @@ -50,14 +68,24 @@ export interface ReactAsyncThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of function executions that have resulted in errors @@ -82,7 +110,14 @@ export interface ReactAsyncThrottler< * { wait: 1000 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Processing...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncThrottler = useAsyncThrottler( * async (id: string) => { * const data = await api.fetchData(id); @@ -163,15 +198,31 @@ export function useAsyncThrottler( options: AsyncThrottlerOptions, selector: (state: AsyncThrottlerState) => TSelected = () => ({}) as TSelected, -): ReactAsyncThrottler { +): PreactAsyncThrottler { const mergedOptions = { ...useDefaultPacerOptions().asyncThrottler, ...options, } as AsyncThrottlerOptions - const [asyncThrottler] = useState( - () => new AsyncThrottler(fn, mergedOptions), - ) + const [asyncThrottler] = useState(() => { + const throttlerInstance = new AsyncThrottler( + fn, + mergedOptions, + ) as unknown as PreactAsyncThrottler + + throttlerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(throttlerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return throttlerInstance + }) asyncThrottler.fn = fn asyncThrottler.setOptions(mergedOptions) @@ -187,7 +238,7 @@ export function useAsyncThrottler( ({ ...asyncThrottler, state, - }) as ReactAsyncThrottler, // omit `store` in favor of `state` + }) as PreactAsyncThrottler, // omit `store` in favor of `state` [asyncThrottler, state], ) } diff --git a/packages/preact-pacer/src/batcher/useBatcher.ts b/packages/preact-pacer/src/batcher/useBatcher.ts index 0d72cd1d..9b349463 100644 --- a/packages/preact-pacer/src/batcher/useBatcher.ts +++ b/packages/preact-pacer/src/batcher/useBatcher.ts @@ -4,11 +4,29 @@ import { useStore } from '@tanstack/preact-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/preact-store' import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' +import type { ComponentChildren } from 'preact' export interface PreactBatcher extends Omit< Batcher, 'store' > { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the batcher state. + * + * This is useful for opting into state re-renders for specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size })}> + * {({ size }) => ( + *
    Batch Size: {size}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: BatcherState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the batcher state changes * @@ -37,14 +55,24 @@ export interface PreactBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of batch executions that have been completed @@ -64,7 +92,14 @@ export interface PreactBatcher extends Omit< * { maxSize: 5, wait: 2000 } * ); * - * // Opt-in to re-render when batch size changes (optimized for displaying queue size) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size })}> + * {({ size }) => ( + *
    Batch Size: {size}
    + * )} + *
    + * + * // Opt-in to re-render when batch size changes at hook level (optimized for displaying queue size) * const batcher = useBatcher( * (items) => console.log('Processing batch:', items), * { maxSize: 5, wait: 2000 }, @@ -132,7 +167,25 @@ export function useBatcher( ...options, } as BatcherOptions - const [batcher] = useState(() => new Batcher(fn, mergedOptions)) + const [batcher] = useState(() => { + const batcherInstance = new Batcher( + fn, + mergedOptions, + ) as unknown as PreactBatcher + + batcherInstance.Subscribe = function Subscribe(props: { + selector: (state: BatcherState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(batcherInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return batcherInstance + }) batcher.fn = fn batcher.setOptions(mergedOptions) diff --git a/packages/preact-pacer/src/debouncer/useDebouncer.ts b/packages/preact-pacer/src/debouncer/useDebouncer.ts index 2e822d6b..066e85ed 100644 --- a/packages/preact-pacer/src/debouncer/useDebouncer.ts +++ b/packages/preact-pacer/src/debouncer/useDebouncer.ts @@ -8,11 +8,29 @@ import type { DebouncerState, } from '@tanstack/pacer/debouncer' import type { AnyFunction } from '@tanstack/pacer/types' +import type { ComponentChildren } from 'preact' export interface PreactDebouncer< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + * + * This is useful for opting into state re-renders for specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: DebouncerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the debouncer state changes * @@ -44,14 +62,24 @@ export interface PreactDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -68,7 +96,14 @@ export interface PreactDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when isPending changes (optimized for loading states) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Searching...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when isPending changes at hook level (optimized for loading states) * const searchDebouncer = useDebouncer( * (query: string) => fetchSearchResults(query), * { wait: 500 }, @@ -112,7 +147,25 @@ export function useDebouncer( ...options, } as DebouncerOptions - const [debouncer] = useState(() => new Debouncer(fn, mergedOptions)) + const [debouncer] = useState(() => { + const debouncerInstance = new Debouncer( + fn, + mergedOptions, + ) as unknown as PreactDebouncer + + debouncerInstance.Subscribe = function Subscribe(props: { + selector: (state: DebouncerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(debouncerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return debouncerInstance + }) debouncer.fn = fn debouncer.setOptions(mergedOptions) diff --git a/packages/preact-pacer/src/queuer/useQueuer.ts b/packages/preact-pacer/src/queuer/useQueuer.ts index b33ba55b..daf4723c 100644 --- a/packages/preact-pacer/src/queuer/useQueuer.ts +++ b/packages/preact-pacer/src/queuer/useQueuer.ts @@ -4,11 +4,29 @@ import { useStore } from '@tanstack/preact-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/preact-store' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' +import type { ComponentChildren } from 'preact' export interface PreactQueuer extends Omit< Queuer, 'store' > { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the queuer state. + * + * This is useful for opting into state re-renders for specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size })}> + * {({ size }) => ( + *
    Queue Size: {size}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: QueuerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the queuer state changes * @@ -42,14 +60,24 @@ export interface PreactQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of items that have been processed by the queuer @@ -73,7 +101,14 @@ export interface PreactQueuer extends Omit< * { started: true, wait: 1000 } * ); * - * // Opt-in to re-render when queue size changes (optimized for displaying queue length) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size })}> + * {({ size }) => ( + *
    Queue Size: {size}
    + * )} + *
    + * + * // Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) * const queue = useQueuer( * (item) => console.log('Processing:', item), * { started: true, wait: 1000 }, @@ -142,7 +177,25 @@ export function useQueuer( ...options, } as QueuerOptions - const [queuer] = useState(() => new Queuer(fn, mergedOptions)) + const [queuer] = useState(() => { + const queuerInstance = new Queuer( + fn, + mergedOptions, + ) as unknown as PreactQueuer + + queuerInstance.Subscribe = function Subscribe(props: { + selector: (state: QueuerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(queuerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return queuerInstance + }) queuer.fn = fn queuer.setOptions(mergedOptions) diff --git a/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts b/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts index 07c37bbf..e2cf4f44 100644 --- a/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/preact-pacer/src/rate-limiter/useRateLimiter.ts @@ -8,11 +8,29 @@ import type { RateLimiterState, } from '@tanstack/pacer/rate-limiter' import type { AnyFunction } from '@tanstack/pacer/types' +import type { ComponentChildren } from 'preact' export interface PreactRateLimiter< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + * + * This is useful for opting into state re-renders for specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejections: {rejectionCount}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the rate limiter state changes * @@ -50,14 +68,24 @@ export interface PreactRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of function executions that have been completed @@ -80,7 +108,14 @@ export interface PreactRateLimiter< * windowType: 'sliding', * }); * - * // Opt-in to re-render when execution count changes (optimized for tracking successful executions) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejections: {rejectionCount}
    + * )} + *
    + * + * // Opt-in to re-render when execution count changes at hook level (optimized for tracking successful executions) * const rateLimiter = useRateLimiter( * apiCall, * { @@ -151,7 +186,25 @@ export function useRateLimiter( ...options, } as RateLimiterOptions - const [rateLimiter] = useState(() => new RateLimiter(fn, mergedOptions)) + const [rateLimiter] = useState(() => { + const rateLimiterInstance = new RateLimiter( + fn, + mergedOptions, + ) as unknown as PreactRateLimiter + + rateLimiterInstance.Subscribe = function Subscribe(props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(rateLimiterInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return rateLimiterInstance + }) rateLimiter.fn = fn rateLimiter.setOptions(mergedOptions) diff --git a/packages/preact-pacer/src/throttler/useThrottler.ts b/packages/preact-pacer/src/throttler/useThrottler.ts index 7ebd7085..b6688b31 100644 --- a/packages/preact-pacer/src/throttler/useThrottler.ts +++ b/packages/preact-pacer/src/throttler/useThrottler.ts @@ -8,11 +8,29 @@ import type { ThrottlerOptions, ThrottlerState, } from '@tanstack/pacer/throttler' +import type { ComponentChildren } from 'preact' export interface PreactThrottler< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Preact HOC (Higher Order Component) that allows you to subscribe to the throttler state. + * + * This is useful for opting into state re-renders for specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) => ComponentChildren /** * Reactive state that will be updated and re-rendered when the throttler state changes * @@ -40,14 +58,24 @@ export interface PreactThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of function executions that have been completed @@ -63,7 +91,14 @@ export interface PreactThrottler< * const [value, setValue] = useState(0); * const throttler = useThrottler(setValue, { wait: 1000 }); * - * // Opt-in to re-render when execution count changes (optimized for tracking executions) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Processing...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution count changes at hook level (optimized for tracking executions) * const [value, setValue] = useState(0); * const throttler = useThrottler( * setValue, @@ -117,7 +152,25 @@ export function useThrottler( ...options, } as ThrottlerOptions - const [throttler] = useState(() => new Throttler(fn, mergedOptions)) + const [throttler] = useState(() => { + const throttlerInstance = new Throttler( + fn, + mergedOptions, + ) as unknown as PreactThrottler + + throttlerInstance.Subscribe = function Subscribe(props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: TSelected) => ComponentChildren) | ComponentChildren + }) { + const selected = useStore(throttlerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return throttlerInstance + }) throttler.fn = fn throttler.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts index 845f3791..edb9b6f9 100644 --- a/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts +++ b/packages/react-pacer/src/async-batcher/useAsyncBatcher.ts @@ -7,11 +7,29 @@ import type { AsyncBatcherOptions, AsyncBatcherState, } from '@tanstack/pacer/async-batcher' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactAsyncBatcher extends Omit< AsyncBatcher, 'store' > { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the batcher state. + * + * This is useful for opting into state re-renders for specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isExecuting: state.isExecuting })}> + * {({ size, isExecuting }) => ( + *
    Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the batcher state changes * @@ -57,14 +75,24 @@ export interface ReactAsyncBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of batch executions that have resulted in errors @@ -93,7 +121,14 @@ export interface ReactAsyncBatcher extends Omit< * { maxSize: 10, wait: 2000 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size, isExecuting: state.isExecuting })}> + * {({ size, isExecuting }) => ( + *
    Batch: {size} items, {isExecuting ? 'Processing' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncBatcher = useAsyncBatcher( * async (items) => { * const results = await Promise.all(items.map(item => processItem(item))); @@ -178,9 +213,25 @@ export function useAsyncBatcher( ...options, } as AsyncBatcherOptions - const [asyncBatcher] = useState( - () => new AsyncBatcher(fn, mergedOptions), - ) + const [asyncBatcher] = useState(() => { + const asyncBatcherInstance = new AsyncBatcher( + fn, + mergedOptions, + ) as unknown as ReactAsyncBatcher + + asyncBatcherInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(asyncBatcherInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return asyncBatcherInstance + }) asyncBatcher.fn = fn asyncBatcher.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts index 36b8ae99..163220d2 100644 --- a/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts +++ b/packages/react-pacer/src/async-debouncer/useAsyncDebouncer.ts @@ -8,11 +8,29 @@ import type { AsyncDebouncerOptions, AsyncDebouncerState, } from '@tanstack/pacer/async-debouncer' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactAsyncDebouncer< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + * + * This is useful for opting into state re-renders for specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isExecuting: state.isExecuting })}> + * {({ isExecuting }) => ( + *
    {isExecuting ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the debouncer state changes * @@ -53,14 +71,24 @@ export interface ReactAsyncDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -84,7 +112,14 @@ export interface ReactAsyncDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isExecuting: state.isExecuting, isPending: state.isPending })}> + * {({ isExecuting, isPending }) => ( + *
    {isExecuting || isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const searchDebouncer = useAsyncDebouncer( * async (query: string) => { * const results = await api.search(query); @@ -158,9 +193,25 @@ export function useAsyncDebouncer( ...options, } as AsyncDebouncerOptions - const [asyncDebouncer] = useState( - () => new AsyncDebouncer(fn, mergedOptions), - ) + const [asyncDebouncer] = useState(() => { + const asyncDebouncerInstance = new AsyncDebouncer( + fn, + mergedOptions, + ) as unknown as ReactAsyncDebouncer + + asyncDebouncerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(asyncDebouncerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return asyncDebouncerInstance + }) asyncDebouncer.fn = fn asyncDebouncer.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts index 4ef2792e..c2add2b0 100644 --- a/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts +++ b/packages/react-pacer/src/async-queuer/useAsyncQueuer.ts @@ -7,11 +7,29 @@ import type { AsyncQueuerOptions, AsyncQueuerState, } from '@tanstack/pacer/async-queuer' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactAsyncQueuer extends Omit< AsyncQueuer, 'store' > { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the queuer state. + * + * This is useful for opting into state re-renders for specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isRunning: state.isRunning })}> + * {({ size, isRunning }) => ( + *
    Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the queuer state changes * @@ -50,14 +68,24 @@ export interface ReactAsyncQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `activeItems`: Items currently being processed by the queuer @@ -88,7 +116,14 @@ export interface ReactAsyncQueuer extends Omit< * { concurrency: 2, maxSize: 100, started: false } * ); * - * // Opt-in to re-render when queue size changes (optimized for displaying queue length) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size, isRunning: state.isRunning })}> + * {({ size, isRunning }) => ( + *
    Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + * + * // Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) * const asyncQueuer = useAsyncQueuer( * async (item) => { * const result = await processItem(item); @@ -178,9 +213,25 @@ export function useAsyncQueuer( ...options, } as AsyncQueuerOptions - const [asyncQueuer] = useState( - () => new AsyncQueuer(fn, mergedOptions), - ) + const [asyncQueuer] = useState(() => { + const asyncQueuerInstance = new AsyncQueuer( + fn, + mergedOptions, + ) as unknown as ReactAsyncQueuer + + asyncQueuerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(asyncQueuerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return asyncQueuerInstance + }) asyncQueuer.fn = fn asyncQueuer.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts index 81bf9f6d..efc461da 100644 --- a/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts +++ b/packages/react-pacer/src/async-rate-limiter/useAsyncRateLimiter.ts @@ -8,11 +8,29 @@ import type { AsyncRateLimiterOptions, AsyncRateLimiterState, } from '@tanstack/pacer/async-rate-limiter' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactAsyncRateLimiter< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + * + * This is useful for opting into state re-renders for specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + * {({ rejectionCount, isExecuting }) => ( + *
    Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the rate limiter state changes * @@ -57,14 +75,24 @@ export interface ReactAsyncRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of function executions that have resulted in errors @@ -86,7 +114,14 @@ export interface ReactAsyncRateLimiter< * { limit: 5, window: 1000 } // 5 calls per second * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + * {({ rejectionCount, isExecuting }) => ( + *
    Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncRateLimiter = useAsyncRateLimiter( * async (id: string) => { * const data = await api.fetchData(id); @@ -190,9 +225,25 @@ export function useAsyncRateLimiter< ...options, } as AsyncRateLimiterOptions - const [asyncRateLimiter] = useState( - () => new AsyncRateLimiter(fn, mergedOptions), - ) + const [asyncRateLimiter] = useState(() => { + const asyncRateLimiterInstance = new AsyncRateLimiter( + fn, + mergedOptions, + ) as unknown as ReactAsyncRateLimiter + + asyncRateLimiterInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(asyncRateLimiterInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return asyncRateLimiterInstance + }) asyncRateLimiter.fn = fn asyncRateLimiter.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts index 1f583c5c..63efd50e 100644 --- a/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts +++ b/packages/react-pacer/src/async-throttler/useAsyncThrottler.ts @@ -8,11 +8,29 @@ import type { AsyncThrottlerOptions, AsyncThrottlerState, } from '@tanstack/pacer/async-throttler' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactAsyncThrottler< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the throttler state. + * + * This is useful for opting into state re-renders for specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isExecuting: state.isExecuting })}> + * {({ isExecuting }) => ( + *
    {isExecuting ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the throttler state changes * @@ -50,14 +68,24 @@ export interface ReactAsyncThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `errorCount`: Number of function executions that have resulted in errors @@ -82,7 +110,14 @@ export interface ReactAsyncThrottler< * { wait: 1000 } * ); * - * // Opt-in to re-render when execution state changes (optimized for loading indicators) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isExecuting: state.isExecuting, isPending: state.isPending })}> + * {({ isExecuting, isPending }) => ( + *
    {isExecuting || isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution state changes at hook level (optimized for loading indicators) * const asyncThrottler = useAsyncThrottler( * async (id: string) => { * const data = await api.fetchData(id); @@ -169,9 +204,25 @@ export function useAsyncThrottler( ...options, } as AsyncThrottlerOptions - const [asyncThrottler] = useState( - () => new AsyncThrottler(fn, mergedOptions), - ) + const [asyncThrottler] = useState(() => { + const asyncThrottlerInstance = new AsyncThrottler( + fn, + mergedOptions, + ) as unknown as ReactAsyncThrottler + + asyncThrottlerInstance.Subscribe = function Subscribe(props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(asyncThrottlerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return asyncThrottlerInstance + }) asyncThrottler.fn = fn asyncThrottler.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/batcher/useBatcher.ts b/packages/react-pacer/src/batcher/useBatcher.ts index ec1d4bc9..1c94809a 100644 --- a/packages/react-pacer/src/batcher/useBatcher.ts +++ b/packages/react-pacer/src/batcher/useBatcher.ts @@ -4,11 +4,29 @@ import { useStore } from '@tanstack/react-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactBatcher extends Omit< Batcher, 'store' > { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the batcher state. + * + * This is useful for opting into state re-renders for specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isPending: state.isPending })}> + * {({ size, isPending }) => ( + *
    Batch: {size} items, {isPending ? 'Pending' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: BatcherState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the batcher state changes * @@ -37,14 +55,24 @@ export interface ReactBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of batch executions that have been completed @@ -64,7 +92,14 @@ export interface ReactBatcher extends Omit< * { maxSize: 5, wait: 2000 } * ); * - * // Opt-in to re-render when batch size changes (optimized for displaying queue size) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size, isPending: state.isPending })}> + * {({ size, isPending }) => ( + *
    Batch: {size} items, {isPending ? 'Pending' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when batch size changes at hook level (optimized for displaying queue size) * const batcher = useBatcher( * (items) => console.log('Processing batch:', items), * { maxSize: 5, wait: 2000 }, @@ -132,7 +167,25 @@ export function useBatcher( ...options, } as BatcherOptions - const [batcher] = useState(() => new Batcher(fn, mergedOptions)) + const [batcher] = useState(() => { + const batcherInstance = new Batcher( + fn, + mergedOptions, + ) as unknown as ReactBatcher + + batcherInstance.Subscribe = function Subscribe(props: { + selector: (state: BatcherState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(batcherInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return batcherInstance + }) batcher.fn = fn batcher.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/debouncer/useDebouncer.ts b/packages/react-pacer/src/debouncer/useDebouncer.ts index 46aec45a..5c80e31f 100644 --- a/packages/react-pacer/src/debouncer/useDebouncer.ts +++ b/packages/react-pacer/src/debouncer/useDebouncer.ts @@ -8,11 +8,29 @@ import type { DebouncerState, } from '@tanstack/pacer/debouncer' import type { AnyFunction } from '@tanstack/pacer/types' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactDebouncer< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the debouncer state. + * + * This is useful for opting into state re-renders for specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: DebouncerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the debouncer state changes * @@ -44,14 +62,24 @@ export interface ReactDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -68,7 +96,14 @@ export interface ReactDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when isPending changes (optimized for loading states) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Searching...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when isPending changes at hook level (optimized for loading states) * const searchDebouncer = useDebouncer( * (query: string) => fetchSearchResults(query), * { wait: 500 }, @@ -112,7 +147,25 @@ export function useDebouncer( ...options, } as DebouncerOptions - const [debouncer] = useState(() => new Debouncer(fn, mergedOptions)) + const [debouncer] = useState(() => { + const debouncerInstance = new Debouncer( + fn, + mergedOptions, + ) as unknown as ReactDebouncer + + debouncerInstance.Subscribe = function Subscribe(props: { + selector: (state: DebouncerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(debouncerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return debouncerInstance + }) debouncer.fn = fn debouncer.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/queuer/useQueuer.ts b/packages/react-pacer/src/queuer/useQueuer.ts index 262d2b35..905b1ff9 100644 --- a/packages/react-pacer/src/queuer/useQueuer.ts +++ b/packages/react-pacer/src/queuer/useQueuer.ts @@ -4,11 +4,29 @@ import { useStore } from '@tanstack/react-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/react-store' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactQueuer extends Omit< Queuer, 'store' > { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the queuer state. + * + * This is useful for opting into state re-renders for specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isRunning: state.isRunning })}> + * {({ size, isRunning }) => ( + *
    Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: QueuerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the queuer state changes * @@ -42,14 +60,24 @@ export interface ReactQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of items that have been processed by the queuer @@ -73,7 +101,14 @@ export interface ReactQueuer extends Omit< * { started: true, wait: 1000 } * ); * - * // Opt-in to re-render when queue size changes (optimized for displaying queue length) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ size: state.size, isRunning: state.isRunning })}> + * {({ size, isRunning }) => ( + *
    Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + * + * // Opt-in to re-render when queue size changes at hook level (optimized for displaying queue length) * const queue = useQueuer( * (item) => console.log('Processing:', item), * { started: true, wait: 1000 }, @@ -142,7 +177,25 @@ export function useQueuer( ...options, } as QueuerOptions - const [queuer] = useState(() => new Queuer(fn, mergedOptions)) + const [queuer] = useState(() => { + const queuerInstance = new Queuer( + fn, + mergedOptions, + ) as unknown as ReactQueuer + + queuerInstance.Subscribe = function Subscribe(props: { + selector: (state: QueuerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(queuerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return queuerInstance + }) queuer.fn = fn queuer.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts index 167abd62..5d58eef0 100644 --- a/packages/react-pacer/src/rate-limiter/useRateLimiter.ts +++ b/packages/react-pacer/src/rate-limiter/useRateLimiter.ts @@ -8,11 +8,29 @@ import type { RateLimiterState, } from '@tanstack/pacer/rate-limiter' import type { AnyFunction } from '@tanstack/pacer/types' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactRateLimiter< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the rate limiter state. + * + * This is useful for opting into state re-renders for specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejected: {rejectionCount} requests
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the rate limiter state changes * @@ -50,14 +68,24 @@ export interface ReactRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of function executions that have been completed @@ -80,7 +108,14 @@ export interface ReactRateLimiter< * windowType: 'sliding', * }); * - * // Opt-in to re-render when execution count changes (optimized for tracking successful executions) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ rejectionCount: state.rejectionCount })}> + * {({ rejectionCount }) => ( + *
    Rejected: {rejectionCount} requests
    + * )} + *
    + * + * // Opt-in to re-render when execution count changes at hook level (optimized for tracking successful executions) * const rateLimiter = useRateLimiter( * apiCall, * { @@ -151,7 +186,25 @@ export function useRateLimiter( ...options, } as RateLimiterOptions - const [rateLimiter] = useState(() => new RateLimiter(fn, mergedOptions)) + const [rateLimiter] = useState(() => { + const rateLimiterInstance = new RateLimiter( + fn, + mergedOptions, + ) as unknown as ReactRateLimiter + + rateLimiterInstance.Subscribe = function Subscribe(props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(rateLimiterInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return rateLimiterInstance + }) rateLimiter.fn = fn rateLimiter.setOptions(mergedOptions) diff --git a/packages/react-pacer/src/throttler/useThrottler.ts b/packages/react-pacer/src/throttler/useThrottler.ts index 33a4f229..a31e1d92 100644 --- a/packages/react-pacer/src/throttler/useThrottler.ts +++ b/packages/react-pacer/src/throttler/useThrottler.ts @@ -8,11 +8,29 @@ import type { ThrottlerOptions, ThrottlerState, } from '@tanstack/pacer/throttler' +import type { FunctionComponent, ReactNode } from 'react' export interface ReactThrottler< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A React HOC (Higher Order Component) that allows you to subscribe to the throttler state. + * + * This is useful for opting into state re-renders for specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Throttling...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) => ReturnType /** * Reactive state that will be updated and re-rendered when the throttler state changes * @@ -40,14 +58,24 @@ export interface ReactThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` HOC (Recommended for component tree subscriptions)** + * + * Use the `Subscribe` HOC 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * The `selector` parameter allows you to specify which state changes will trigger a re-render + * at the hook level, optimizing performance by preventing unnecessary re-renders when irrelevant + * state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` HOC. This prevents unnecessary + * re-renders and gives you full control over when your component updates. * * Available state properties: * - `executionCount`: Number of function executions that have been completed @@ -63,7 +91,14 @@ export interface ReactThrottler< * const [value, setValue] = useState(0); * const throttler = useThrottler(setValue, { wait: 1000 }); * - * // Opt-in to re-render when execution count changes (optimized for tracking executions) + * // Subscribe to state changes deep in component tree using Subscribe HOC + * ({ isPending: state.isPending })}> + * {({ isPending }) => ( + *
    {isPending ? 'Throttling...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to re-render when execution count changes at hook level (optimized for tracking executions) * const [value, setValue] = useState(0); * const throttler = useThrottler( * setValue, @@ -117,7 +152,25 @@ export function useThrottler( ...options, } as ThrottlerOptions - const [throttler] = useState(() => new Throttler(fn, mergedOptions)) + const [throttler] = useState(() => { + const throttlerInstance = new Throttler( + fn, + mergedOptions, + ) as unknown as ReactThrottler + + throttlerInstance.Subscribe = function Subscribe(props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: TSelected) => ReactNode) | ReactNode + }) { + const selected = useStore(throttlerInstance.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } + + return throttlerInstance + }) throttler.fn = fn throttler.setOptions(mergedOptions) diff --git a/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts b/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts index 7a6d840a..44a01986 100644 --- a/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts +++ b/packages/solid-pacer/src/async-batcher/createAsyncBatcher.ts @@ -2,7 +2,7 @@ import { AsyncBatcher } from '@tanstack/pacer/async-batcher' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AsyncBatcherOptions, AsyncBatcherState, @@ -12,6 +12,23 @@ export interface SolidAsyncBatcher extends Omit< AsyncBatcher, 'store' > { + /** + * A Solid component that allows you to subscribe to the batcher state. + * + * This is useful for tracking specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isExecuting: state.isExecuting })}> + * {(state) => ( + *
    Batch: {state().size} items, {state().isExecuting ? 'Executing...' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the batcher state changes * @@ -58,14 +75,24 @@ export interface SolidAsyncBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `errorCount`: Number of failed batch executions @@ -98,7 +125,7 @@ export interface SolidAsyncBatcher extends Omit< * } * ); * - * // Opt-in to re-render when items or isExecuting changes (optimized for UI updates) + * // Opt-in to track items or isExecuting changes (optimized for UI updates) * const asyncBatcher = createAsyncBatcher( * async (items) => { * const results = await Promise.all(items.map(item => processItem(item))); @@ -108,7 +135,7 @@ export interface SolidAsyncBatcher extends Omit< * (state) => ({ items: state.items, isExecuting: state.isExecuting }) * ); * - * // Opt-in to re-render when error state changes (optimized for error handling) + * // Opt-in to track error state changes (optimized for error handling) * const asyncBatcher = createAsyncBatcher( * async (items) => { * const results = await Promise.all(items.map(item => processItem(item))); @@ -139,7 +166,21 @@ export function createAsyncBatcher( ...options, } as AsyncBatcherOptions - const asyncBatcher = new AsyncBatcher(fn, mergedOptions) + const asyncBatcher = new AsyncBatcher( + fn, + mergedOptions, + ) as unknown as SolidAsyncBatcher + + asyncBatcher.Subscribe = function Subscribe(props: { + selector: (state: AsyncBatcherState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncBatcher.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncBatcher.store, selector) diff --git a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts index 3dddbd70..65ae9601 100644 --- a/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts +++ b/packages/solid-pacer/src/async-debouncer/createAsyncDebouncer.ts @@ -3,7 +3,7 @@ import { useStore } from '@tanstack/solid-store' import { createEffect, onCleanup } from 'solid-js' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AsyncDebouncerOptions, AsyncDebouncerState, @@ -14,6 +14,23 @@ export interface SolidAsyncDebouncer< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the debouncer state. + * + * This is useful for tracking specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending, isExecuting: state.isExecuting })}> + * {(state) => ( + *
    {state().isPending ? 'Waiting...' : state().isExecuting ? 'Executing...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the debouncer state changes * @@ -54,14 +71,24 @@ export interface SolidAsyncDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -85,7 +112,7 @@ export interface SolidAsyncDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when isPending or isExecuting changes (optimized for loading states) + * // Opt-in to track isPending or isExecuting changes (optimized for loading states) * const debouncer = createAsyncDebouncer( * async (query: string) => { * const results = await api.search(query); @@ -95,7 +122,7 @@ export interface SolidAsyncDebouncer< * (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting }) * ); * - * // Opt-in to re-render when error state changes (optimized for error handling) + * // Opt-in to track error state changes (optimized for error handling) * const debouncer = createAsyncDebouncer( * async (searchTerm) => { * const data = await searchAPI(searchTerm); @@ -130,7 +157,21 @@ export function createAsyncDebouncer< ...options, } as AsyncDebouncerOptions - const asyncDebouncer = new AsyncDebouncer(fn, mergedOptions) + const asyncDebouncer = new AsyncDebouncer( + fn, + mergedOptions, + ) as unknown as SolidAsyncDebouncer + + asyncDebouncer.Subscribe = function Subscribe(props: { + selector: (state: AsyncDebouncerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncDebouncer.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncDebouncer.store, selector) diff --git a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts index 30d23684..f7360844 100644 --- a/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts +++ b/packages/solid-pacer/src/async-queuer/createAsyncQueuer.ts @@ -2,7 +2,7 @@ import { AsyncQueuer } from '@tanstack/pacer/async-queuer' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AsyncQueuerOptions, AsyncQueuerState, @@ -12,6 +12,23 @@ export interface SolidAsyncQueuer extends Omit< AsyncQueuer, 'store' > { + /** + * A Solid component that allows you to subscribe to the queuer state. + * + * This is useful for tracking specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ pendingItems: state.pendingItems, activeItems: state.activeItems })}> + * {(state) => ( + *
    Pending: {state().pendingItems.length}, Active: {state().activeItems.length}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the queuer state changes * @@ -51,14 +68,24 @@ export interface SolidAsyncQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `activeItems`: Array of items currently being processed @@ -88,7 +115,7 @@ export interface SolidAsyncQueuer extends Omit< * } * }); * - * // Opt-in to re-render when queue state changes (optimized for UI updates) + * // Opt-in to track queue state changes (optimized for UI updates) * const asyncQueuer = createAsyncQueuer( * async (item) => await fetchData(item), * { concurrency: 2, started: true }, @@ -99,7 +126,7 @@ export interface SolidAsyncQueuer extends Omit< * }) * ); * - * // Opt-in to re-render when processing metrics change (optimized for tracking progress) + * // Opt-in to track processing metrics changes (optimized for tracking progress) * const asyncQueuer = createAsyncQueuer( * async (item) => await fetchData(item), * { concurrency: 2, started: true }, @@ -131,7 +158,21 @@ export function createAsyncQueuer( ...options, } as AsyncQueuerOptions - const asyncQueuer = new AsyncQueuer(fn, mergedOptions) + const asyncQueuer = new AsyncQueuer( + fn, + mergedOptions, + ) as unknown as SolidAsyncQueuer + + asyncQueuer.Subscribe = function Subscribe(props: { + selector: (state: AsyncQueuerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncQueuer.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncQueuer.store, selector) diff --git a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts index 36b62c1d..2970d235 100644 --- a/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts +++ b/packages/solid-pacer/src/async-rate-limiter/createAsyncRateLimiter.ts @@ -2,7 +2,7 @@ import { AsyncRateLimiter } from '@tanstack/pacer/async-rate-limiter' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncRateLimiterOptions, @@ -13,6 +13,23 @@ export interface SolidAsyncRateLimiter< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the rate limiter state. + * + * This is useful for tracking specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + * {(state) => ( + *
    Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the rate limiter state changes * @@ -64,14 +81,24 @@ export interface SolidAsyncRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `currentWindowStart`: Timestamp when the current window started @@ -87,7 +114,7 @@ export interface SolidAsyncRateLimiter< * @example * ```tsx * // Default behavior - no reactive state subscriptions - * const { maybeExecute } = createAsyncRateLimiter( + * const asyncRateLimiter = createAsyncRateLimiter( * async (id: string) => { * const data = await api.fetchData(id); * return data; // Return value is preserved @@ -95,36 +122,81 @@ export interface SolidAsyncRateLimiter< * { limit: 5, window: 1000 } // 5 calls per second * ); * - * // Opt-in to re-render when rate limit and execution state changes (optimized for UI feedback) - * const rateLimiter = createAsyncRateLimiter( - * async (query) => { - * const result = await searchAPI(query); - * return result; + * // Subscribe to state changes deep in component tree using Subscribe component + * ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> + * {(state) => ( + *
    Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}
    + * )} + *
    + * + * // Opt-in to track execution state changes at hook level (optimized for loading indicators) + * const asyncRateLimiter = createAsyncRateLimiter( + * async (id: string) => { + * const data = await api.fetchData(id); + * return data; * }, - * { limit: 10, window: 60000 }, + * { limit: 5, window: 1000 }, + * (state) => ({ isExecuting: state.isExecuting }) + * ); + * + * // Opt-in to track results when available (optimized for data display) + * const asyncRateLimiter = createAsyncRateLimiter( + * async (id: string) => { + * const data = await api.fetchData(id); + * return data; + * }, + * { limit: 5, window: 1000 }, * (state) => ({ - * remainingInWindow: state.remainingInWindow, - * isExecuting: state.isExecuting, - * rejectionCount: state.rejectionCount + * lastResult: state.lastResult, + * successCount: state.successCount * }) * ); * - * // Opt-in to re-render when error state changes (optimized for error handling) - * const rateLimiter = createAsyncRateLimiter( - * async (query) => { - * const result = await searchAPI(query); - * return result; + * // Opt-in to track error/rejection state changes (optimized for error handling) + * const asyncRateLimiter = createAsyncRateLimiter( + * async (id: string) => { + * const data = await api.fetchData(id); + * return data; * }, * { - * limit: 10, - * window: 60000, // 10 calls per minute - * onReject: (info) => console.log(`Rate limit exceeded: ${info.nextValidTime - Date.now()}ms until next window`) + * limit: 5, + * window: 1000, + * onError: (error) => console.error('API call failed:', error), + * onReject: (rateLimiter) => console.log('Rate limit exceeded') * }, - * (state) => ({ hasError: state.hasError, lastError: state.lastError }) + * (state) => ({ + * errorCount: state.errorCount, + * rejectionCount: state.rejectionCount + * }) + * ); + * + * // Opt-in to track execution metrics changes (optimized for stats display) + * const asyncRateLimiter = createAsyncRateLimiter( + * async (id: string) => { + * const data = await api.fetchData(id); + * return data; + * }, + * { limit: 5, window: 1000 }, + * (state) => ({ + * successCount: state.successCount, + * errorCount: state.errorCount, + * settleCount: state.settleCount, + * rejectionCount: state.rejectionCount + * }) + * ); + * + * // Opt-in to track execution times changes (optimized for window calculations) + * const asyncRateLimiter = createAsyncRateLimiter( + * async (id: string) => { + * const data = await api.fetchData(id); + * return data; + * }, + * { limit: 5, window: 1000 }, + * (state) => ({ executionTimes: state.executionTimes }) * ); * * // Access the selected state (will be empty object {} unless selector provided) - * const { remainingInWindow, isExecuting } = rateLimiter.state(); + * const { isExecuting, lastResult, rejectionCount } = asyncRateLimiter.state(); * ``` */ export function createAsyncRateLimiter< @@ -141,7 +213,21 @@ export function createAsyncRateLimiter< ...options, } as AsyncRateLimiterOptions - const asyncRateLimiter = new AsyncRateLimiter(fn, mergedOptions) + const asyncRateLimiter = new AsyncRateLimiter( + fn, + mergedOptions, + ) as unknown as SolidAsyncRateLimiter + + asyncRateLimiter.Subscribe = function Subscribe(props: { + selector: (state: AsyncRateLimiterState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncRateLimiter.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncRateLimiter.store, selector) diff --git a/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts b/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts index 1d0c49fe..440d709b 100644 --- a/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts +++ b/packages/solid-pacer/src/async-throttler/createAsyncThrottler.ts @@ -2,7 +2,7 @@ import { AsyncThrottler } from '@tanstack/pacer/async-throttler' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AnyAsyncFunction } from '@tanstack/pacer/types' import type { AsyncThrottlerOptions, @@ -13,6 +13,23 @@ export interface SolidAsyncThrottler< TFn extends AnyAsyncFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the throttler state. + * + * This is useful for tracking specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending, isExecuting: state.isExecuting })}> + * {(state) => ( + *
    {state().isPending ? 'Pending...' : state().isExecuting ? 'Executing...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the throttler state changes * @@ -50,14 +67,24 @@ export interface SolidAsyncThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `canLeadingExecute`: Whether the throttler can execute on the leading edge @@ -84,7 +111,7 @@ export interface SolidAsyncThrottler< * { wait: 1000 } * ); * - * // Opt-in to re-render when isPending or isExecuting changes (optimized for loading states) + * // Opt-in to track isPending or isExecuting changes (optimized for loading states) * const throttler = createAsyncThrottler( * async (query) => { * const result = await searchAPI(query); @@ -94,7 +121,7 @@ export interface SolidAsyncThrottler< * (state) => ({ isPending: state.isPending, isExecuting: state.isExecuting }) * ); * - * // Opt-in to re-render when error state changes (optimized for error handling) + * // Opt-in to track error state changes (optimized for error handling) * const throttler = createAsyncThrottler( * async (query) => { * const result = await searchAPI(query); @@ -129,7 +156,21 @@ export function createAsyncThrottler< ...options, } as AsyncThrottlerOptions - const asyncThrottler = new AsyncThrottler(fn, mergedOptions) + const asyncThrottler = new AsyncThrottler( + fn, + mergedOptions, + ) as unknown as SolidAsyncThrottler + + asyncThrottler.Subscribe = function Subscribe(props: { + selector: (state: AsyncThrottlerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncThrottler.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncThrottler.store, selector) diff --git a/packages/solid-pacer/src/batcher/createBatcher.ts b/packages/solid-pacer/src/batcher/createBatcher.ts index ef9ea220..e6d16bfb 100644 --- a/packages/solid-pacer/src/batcher/createBatcher.ts +++ b/packages/solid-pacer/src/batcher/createBatcher.ts @@ -2,13 +2,30 @@ import { Batcher } from '@tanstack/pacer/batcher' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { BatcherOptions, BatcherState } from '@tanstack/pacer/batcher' export interface SolidBatcher extends Omit< Batcher, 'store' > { + /** + * A Solid component that allows you to subscribe to the batcher state. + * + * This is useful for tracking specific parts of the batcher state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isRunning: state.isRunning })}> + * {(state) => ( + *
    Batch: {state().size} items, {state().isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: BatcherState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the batcher state changes * @@ -40,14 +57,24 @@ export interface SolidBatcher extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `batcher.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `executionCount`: Number of batch executions that have been completed @@ -71,14 +98,14 @@ export interface SolidBatcher extends Omit< * } * ); * - * // Opt-in to re-render when items or isRunning changes (optimized for UI updates) + * // Opt-in to track items or isRunning changes (optimized for UI updates) * const batcher = createBatcher( * (items) => console.log('Processing batch:', items), * { maxSize: 5, wait: 2000 }, * (state) => ({ items: state.items, isRunning: state.isRunning }) * ); * - * // Opt-in to re-render when execution metrics change (optimized for tracking progress) + * // Opt-in to track execution metrics changes (optimized for tracking progress) * const batcher = createBatcher( * (items) => console.log('Processing batch:', items), * { maxSize: 5, wait: 2000 }, @@ -111,7 +138,21 @@ export function createBatcher( ...options, } as BatcherOptions - const batcher = new Batcher(fn, mergedOptions) + const batcher = new Batcher(fn, mergedOptions) as unknown as SolidBatcher< + TValue, + TSelected + > + + batcher.Subscribe = function Subscribe(props: { + selector: (state: BatcherState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(batcher.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(batcher.store, selector) return { diff --git a/packages/solid-pacer/src/debouncer/createDebouncer.ts b/packages/solid-pacer/src/debouncer/createDebouncer.ts index 9a232efd..fc7dc027 100644 --- a/packages/solid-pacer/src/debouncer/createDebouncer.ts +++ b/packages/solid-pacer/src/debouncer/createDebouncer.ts @@ -3,7 +3,7 @@ import { createEffect, onCleanup } from 'solid-js' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' import type { DebouncerOptions, @@ -14,6 +14,23 @@ export interface SolidDebouncer< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the debouncer state. + * + * This is useful for tracking specific parts of the debouncer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {(state) => ( + *
    {state().isPending ? 'Waiting...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: DebouncerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the debouncer state changes * @@ -45,14 +62,24 @@ export interface SolidDebouncer< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `debouncer.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `canLeadingExecute`: Whether the debouncer can execute on the leading edge @@ -69,21 +96,21 @@ export interface SolidDebouncer< * { wait: 500 } * ); * - * // Opt-in to re-render when isPending changes (optimized for loading states) + * // Opt-in to track isPending changes (optimized for loading states) * const debouncer = createDebouncer( * (query: string) => fetchSearchResults(query), * { wait: 500 }, * (state) => ({ isPending: state.isPending }) * ); * - * // Opt-in to re-render when executionCount changes (optimized for tracking execution) + * // Opt-in to track executionCount changes (optimized for tracking execution) * const debouncer = createDebouncer( * (query: string) => fetchSearchResults(query), * { wait: 500 }, * (state) => ({ executionCount: state.executionCount }) * ); * - * // Multiple state properties - re-render when any of these change + * // Multiple state properties - track when any of these change * const debouncer = createDebouncer( * (query: string) => fetchSearchResults(query), * { wait: 500 }, @@ -113,7 +140,21 @@ export function createDebouncer( ...options, } as DebouncerOptions - const asyncDebouncer = new Debouncer(fn, mergedOptions) + const asyncDebouncer = new Debouncer( + fn, + mergedOptions, + ) as unknown as SolidDebouncer + + asyncDebouncer.Subscribe = function Subscribe(props: { + selector: (state: DebouncerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncDebouncer.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncDebouncer.store, selector) diff --git a/packages/solid-pacer/src/queuer/createQueuedSignal.ts b/packages/solid-pacer/src/queuer/createQueuedSignal.ts index 77a2833a..a3da6b9e 100644 --- a/packages/solid-pacer/src/queuer/createQueuedSignal.ts +++ b/packages/solid-pacer/src/queuer/createQueuedSignal.ts @@ -20,13 +20,13 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' * ## State Management and Selector * * The primitive uses Solid's reactive state management via the underlying queuer instance. - * The `selector` parameter allows you to specify which queuer state changes will trigger a re-render, - * optimizing performance by preventing unnecessary re-renders when irrelevant state changes occur. + * The `selector` parameter allows you to specify which queuer state changes will trigger reactive updates, + * optimizing performance by preventing unnecessary updates when irrelevant state changes occur. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function. This prevents unnecessary updates and gives you + * full control over when your component tracks state changes. Only when you provide a selector will the + * component track changes to the selected state values. * * Available queuer state properties: * - `executionCount`: Number of items that have been processed by the queuer @@ -55,7 +55,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' * } * ); * - * // Opt-in to re-render when queue contents change (optimized for displaying queue items) + * // Opt-in to track queue contents changes (optimized for displaying queue items) * const [items, addItem, queue] = createQueuedSignal( * (item) => console.log('Processing:', item), * { started: true, wait: 1000 }, @@ -66,7 +66,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' * }) * ); * - * // Opt-in to re-render when processing state changes (optimized for loading indicators) + * // Opt-in to track processing state changes (optimized for loading indicators) * const [items, addItem, queue] = createQueuedSignal( * (item) => console.log('Processing:', item), * { started: true, wait: 1000 }, @@ -78,7 +78,7 @@ import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' * }) * ); * - * // Opt-in to re-render when execution metrics change (optimized for stats display) + * // Opt-in to track execution metrics changes (optimized for stats display) * const [items, addItem, queue] = createQueuedSignal( * (item) => console.log('Processing:', item), * { started: true, wait: 1000 }, diff --git a/packages/solid-pacer/src/queuer/createQueuer.ts b/packages/solid-pacer/src/queuer/createQueuer.ts index 1b6d1e0c..4ea3707b 100644 --- a/packages/solid-pacer/src/queuer/createQueuer.ts +++ b/packages/solid-pacer/src/queuer/createQueuer.ts @@ -2,13 +2,30 @@ import { Queuer } from '@tanstack/pacer/queuer' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { QueuerOptions, QueuerState } from '@tanstack/pacer/queuer' export interface SolidQueuer extends Omit< Queuer, 'store' > { + /** + * A Solid component that allows you to subscribe to the queuer state. + * + * This is useful for tracking specific parts of the queuer state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ size: state.size, isRunning: state.isRunning })}> + * {(state) => ( + *
    Queue: {state().size} items, {state().isRunning ? 'Processing' : 'Idle'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: QueuerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the queuer state changes * @@ -42,14 +59,24 @@ export interface SolidQueuer extends Omit< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `queuer.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `executionCount`: Number of items that have been processed @@ -72,14 +99,14 @@ export interface SolidQueuer extends Omit< * } * ); * - * // Opt-in to re-render when items or isRunning changes (optimized for UI updates) + * // Opt-in to track items or isRunning changes (optimized for UI updates) * const queue = createQueuer( * (item) => console.log('Processing', item), * { started: true, wait: 1000 }, * (state) => ({ items: state.items, isRunning: state.isRunning }) * ); * - * // Opt-in to re-render when execution metrics change (optimized for tracking progress) + * // Opt-in to track execution metrics changes (optimized for tracking progress) * const queue = createQueuer( * (item) => console.log('Processing', item), * { started: true, wait: 1000 }, @@ -111,7 +138,21 @@ export function createQueuer( ...options, } as QueuerOptions - const queuer = new Queuer(fn, mergedOptions) + const queuer = new Queuer(fn, mergedOptions) as unknown as SolidQueuer< + TValue, + TSelected + > + + queuer.Subscribe = function Subscribe(props: { + selector: (state: QueuerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(queuer.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(queuer.store, selector) diff --git a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts index cb364e92..53efeb2f 100644 --- a/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts +++ b/packages/solid-pacer/src/rate-limiter/createRateLimiter.ts @@ -2,7 +2,7 @@ import { RateLimiter } from '@tanstack/pacer/rate-limiter' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' import type { RateLimiterOptions, @@ -13,6 +13,23 @@ export interface SolidRateLimiter< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the rate limiter state. + * + * This is useful for tracking specific parts of the rate limiter state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ rejectionCount: state.rejectionCount })}> + * {(state) => ( + *
    Rejections: {state().rejectionCount}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the rate limiter state changes * @@ -50,14 +67,24 @@ export interface SolidRateLimiter< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `rateLimiter.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `executionCount`: Number of function executions that have been completed @@ -73,33 +100,74 @@ export interface SolidRateLimiter< * limit: 5, * window: 60000, * windowType: 'sliding', - * onReject: (rateLimiter) => { - * console.log(`Rate limit exceeded. Try again in ${rateLimiter.getMsUntilNextWindow()}ms`); - * } * }); * - * // Opt-in to re-render when rate limit state changes (optimized for UI feedback) + * // Subscribe to state changes deep in component tree using Subscribe component + * ({ rejectionCount: state.rejectionCount })}> + * {(state) => ( + *
    Rejections: {state().rejectionCount}
    + * )} + *
    + * + * // Opt-in to track execution count changes at hook level (optimized for tracking successful executions) * const rateLimiter = createRateLimiter( * apiCall, - * { limit: 5, window: 60000 }, - * (state) => ({ - * remainingInWindow: state.remainingInWindow, - * rejectionCount: state.rejectionCount - * }) + * { + * limit: 5, + * window: 60000, + * windowType: 'sliding', + * }, + * (state) => ({ executionCount: state.executionCount }) * ); * - * // Opt-in to re-render when execution metrics change (optimized for tracking progress) + * // Opt-in to track rejection count changes (optimized for tracking rate limit violations) * const rateLimiter = createRateLimiter( * apiCall, - * { limit: 5, window: 60000 }, + * { + * limit: 5, + * window: 60000, + * windowType: 'sliding', + * }, + * (state) => ({ rejectionCount: state.rejectionCount }) + * ); + * + * // Opt-in to track execution times changes (optimized for window calculations) + * const rateLimiter = createRateLimiter( + * apiCall, + * { + * limit: 5, + * window: 60000, + * windowType: 'sliding', + * }, + * (state) => ({ executionTimes: state.executionTimes }) + * ); + * + * // Multiple state properties - track when any of these change + * const rateLimiter = createRateLimiter( + * apiCall, + * { + * limit: 5, + * window: 60000, + * windowType: 'sliding', + * }, * (state) => ({ * executionCount: state.executionCount, - * nextWindowTime: state.nextWindowTime + * rejectionCount: state.rejectionCount * }) * ); * + * // Monitor rate limit status + * const handleClick = () => { + * const remaining = rateLimiter.getRemainingInWindow(); + * if (remaining > 0) { + * rateLimiter.maybeExecute(data); + * } else { + * showRateLimitWarning(); + * } + * }; + * * // Access the selected state (will be empty object {} unless selector provided) - * const { remainingInWindow, rejectionCount } = rateLimiter.state(); + * const { executionCount, rejectionCount } = rateLimiter.state(); * ``` */ export function createRateLimiter( @@ -112,7 +180,21 @@ export function createRateLimiter( ...options, } as RateLimiterOptions - const rateLimiter = new RateLimiter(fn, mergedOptions) + const rateLimiter = new RateLimiter( + fn, + mergedOptions, + ) as unknown as SolidRateLimiter + + rateLimiter.Subscribe = function Subscribe(props: { + selector: (state: RateLimiterState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(rateLimiter.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(rateLimiter.store, selector) diff --git a/packages/solid-pacer/src/throttler/createThrottledSignal.ts b/packages/solid-pacer/src/throttler/createThrottledSignal.ts index 3f02863a..4d7cf76f 100644 --- a/packages/solid-pacer/src/throttler/createThrottledSignal.ts +++ b/packages/solid-pacer/src/throttler/createThrottledSignal.ts @@ -12,7 +12,7 @@ import type { * This hook combines Solid's createSignal with throttling functionality to provide controlled state updates. * * Throttling ensures state updates occur at a controlled rate regardless of how frequently the setter is called. - * This is useful for rate-limiting expensive re-renders or operations that depend on rapidly changing state. + * This is useful for rate-limiting expensive updates or operations that depend on rapidly changing state. * * The hook returns a tuple containing: * - The throttled state value accessor diff --git a/packages/solid-pacer/src/throttler/createThrottledValue.ts b/packages/solid-pacer/src/throttler/createThrottledValue.ts index a2d2d3e3..d98504a8 100644 --- a/packages/solid-pacer/src/throttler/createThrottledValue.ts +++ b/packages/solid-pacer/src/throttler/createThrottledValue.ts @@ -12,7 +12,7 @@ import type { * This hook uses Solid's createSignal internally to manage the throttled state. * * Throttling ensures the value updates occur at a controlled rate regardless of how frequently the input value changes. - * This is useful for rate-limiting expensive re-renders or API calls that depend on rapidly changing values. + * This is useful for rate-limiting expensive updates or API calls that depend on rapidly changing values. * * The hook returns a tuple containing: * - An accessor function that provides the throttled value diff --git a/packages/solid-pacer/src/throttler/createThrottler.ts b/packages/solid-pacer/src/throttler/createThrottler.ts index ea07264c..99ce7b40 100644 --- a/packages/solid-pacer/src/throttler/createThrottler.ts +++ b/packages/solid-pacer/src/throttler/createThrottler.ts @@ -3,7 +3,7 @@ import { createEffect, onCleanup } from 'solid-js' import { useStore } from '@tanstack/solid-store' import { useDefaultPacerOptions } from '../provider/PacerProvider' import type { Store } from '@tanstack/solid-store' -import type { Accessor } from 'solid-js' +import type { Accessor, JSX } from 'solid-js' import type { AnyFunction } from '@tanstack/pacer/types' import type { ThrottlerOptions, @@ -14,6 +14,23 @@ export interface SolidThrottler< TFn extends AnyFunction, TSelected = {}, > extends Omit, 'store'> { + /** + * A Solid component that allows you to subscribe to the throttler state. + * + * This is useful for tracking specific parts of the throttler state + * deep in your component tree without needing to pass a selector to the hook. + * + * @example + * ({ isPending: state.isPending })}> + * {(state) => ( + *
    {state().isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + */ + Subscribe: (props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) => JSX.Element /** * Reactive state that will be updated when the throttler state changes * @@ -41,14 +58,24 @@ export interface SolidThrottler< * * ## State Management and Selector * - * The hook uses TanStack Store for reactive state management. The `selector` parameter allows you - * to specify which state changes will trigger a re-render, optimizing performance by preventing - * unnecessary re-renders when irrelevant state changes occur. + * The hook uses TanStack Store for reactive state management. You can subscribe to state changes + * in two ways: + * + * **1. Using `throttler.Subscribe` component (Recommended for component tree subscriptions)** + * + * 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. + * + * **2. Using the `selector` parameter (For hook-level subscriptions)** + * + * 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. * * **By default, there will be no reactive state subscriptions** and you must opt-in to state - * tracking by providing a selector function. This prevents unnecessary re-renders and gives you - * full control over when your component updates. Only when you provide a selector will the - * component re-render when the selected state values change. + * tracking by providing a selector function or using the `Subscribe` component. This prevents unnecessary + * updates and gives you full control over when your component tracks state changes. * * Available state properties: * - `canLeadingExecute`: Whether the throttler can execute on the leading edge @@ -65,21 +92,28 @@ export interface SolidThrottler< * // Default behavior - no reactive state subscriptions * const throttler = createThrottler(setValue, { wait: 1000 }); * - * // Opt-in to re-render when isPending changes (optimized for loading states) + * // Subscribe to state changes deep in component tree using Subscribe component + * ({ isPending: state.isPending })}> + * {(state) => ( + *
    {state().isPending ? 'Loading...' : 'Ready'}
    + * )} + *
    + * + * // Opt-in to track isPending changes at hook level (optimized for loading states) * const throttler = createThrottler( * setValue, * { wait: 1000 }, * (state) => ({ isPending: state.isPending }) * ); * - * // Opt-in to re-render when executionCount changes (optimized for tracking execution) + * // Opt-in to track executionCount changes (optimized for tracking execution) * const throttler = createThrottler( * setValue, * { wait: 1000 }, * (state) => ({ executionCount: state.executionCount }) * ); * - * // Multiple state properties - re-render when any of these change + * // Multiple state properties - track when any of these change * const throttler = createThrottler( * setValue, * { @@ -109,7 +143,21 @@ export function createThrottler( ...options, } as ThrottlerOptions - const asyncThrottler = new Throttler(fn, mergedOptions) + const asyncThrottler = new Throttler( + fn, + mergedOptions, + ) as unknown as SolidThrottler + + asyncThrottler.Subscribe = function Subscribe(props: { + selector: (state: ThrottlerState) => TSelected + children: ((state: Accessor) => JSX.Element) | JSX.Element + }) { + const selected = useStore(asyncThrottler.store, props.selector) + + return typeof props.children === 'function' + ? props.children(selected) + : props.children + } const state = useStore(asyncThrottler.store, selector) From 7a4911c9e4f9cf8dea3baf0b06bc5adfd44b973c Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:01:47 +0000 Subject: [PATCH 2/2] ci: apply automated fixes --- .../solid/reference/functions/createAsyncRateLimiter.md | 4 ++-- docs/framework/solid/reference/functions/createRateLimiter.md | 4 ++-- docs/framework/solid/reference/functions/createThrottler.md | 4 ++-- .../framework/solid/reference/interfaces/SolidAsyncBatcher.md | 4 ++-- .../solid/reference/interfaces/SolidAsyncDebouncer.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md | 4 ++-- .../solid/reference/interfaces/SolidAsyncRateLimiter.md | 4 ++-- .../solid/reference/interfaces/SolidAsyncThrottler.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidBatcher.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidDebouncer.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidQueuer.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidRateLimiter.md | 4 ++-- docs/framework/solid/reference/interfaces/SolidThrottler.md | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/framework/solid/reference/functions/createAsyncRateLimiter.md b/docs/framework/solid/reference/functions/createAsyncRateLimiter.md index f2d4691e..e66f8270 100644 --- a/docs/framework/solid/reference/functions/createAsyncRateLimiter.md +++ b/docs/framework/solid/reference/functions/createAsyncRateLimiter.md @@ -123,8 +123,8 @@ const asyncRateLimiter = createAsyncRateLimiter( // Subscribe to state changes deep in component tree using Subscribe component ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> - {({ rejectionCount, isExecuting }) => ( -
    Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
    + {(state) => ( +
    Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}
    )}
    diff --git a/docs/framework/solid/reference/functions/createRateLimiter.md b/docs/framework/solid/reference/functions/createRateLimiter.md index e27c3bb8..91a45f49 100644 --- a/docs/framework/solid/reference/functions/createRateLimiter.md +++ b/docs/framework/solid/reference/functions/createRateLimiter.md @@ -103,8 +103,8 @@ const rateLimiter = createRateLimiter(apiCall, { // Subscribe to state changes deep in component tree using Subscribe component ({ rejectionCount: state.rejectionCount })}> - {({ rejectionCount }) => ( -
    Rejections: {rejectionCount}
    + {(state) => ( +
    Rejections: {state().rejectionCount}
    )}
    diff --git a/docs/framework/solid/reference/functions/createThrottler.md b/docs/framework/solid/reference/functions/createThrottler.md index 88eb6377..300d395d 100644 --- a/docs/framework/solid/reference/functions/createThrottler.md +++ b/docs/framework/solid/reference/functions/createThrottler.md @@ -92,8 +92,8 @@ const throttler = createThrottler(setValue, { wait: 1000 }); // Subscribe to state changes deep in component tree using Subscribe component ({ isPending: state.isPending })}> - {({ isPending }) => ( -
    {isPending ? 'Loading...' : 'Ready'}
    + {(state) => ( +
    {state().isPending ? 'Loading...' : 'Ready'}
    )}
    diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md b/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md index 89dcb056..16ebce0d 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncBatcher.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ size: state.size, isExecuting: state.isExecuting })}> - {({ size, isExecuting }) => ( -
    Batch: {size} items, {isExecuting ? 'Executing...' : 'Idle'}
    + {(state) => ( +
    Batch: {state().size} items, {state().isExecuting ? 'Executing...' : 'Idle'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md b/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md index 152b505f..175e9cb0 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncDebouncer.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ isPending: state.isPending, isExecuting: state.isExecuting })}> - {({ isPending, isExecuting }) => ( -
    {isPending ? 'Waiting...' : isExecuting ? 'Executing...' : 'Ready'}
    + {(state) => ( +
    {state().isPending ? 'Waiting...' : state().isExecuting ? 'Executing...' : 'Ready'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md b/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md index 0729759b..ceaef51d 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncQueuer.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ pendingItems: state.pendingItems, activeItems: state.activeItems })}> - {({ pendingItems, activeItems }) => ( -
    Pending: {pendingItems.length}, Active: {activeItems.length}
    + {(state) => ( +
    Pending: {state().pendingItems.length}, Active: {state().activeItems.length}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md b/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md index c26dae9e..614fc4b5 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncRateLimiter.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ rejectionCount: state.rejectionCount, isExecuting: state.isExecuting })}> - {({ rejectionCount, isExecuting }) => ( -
    Rejected: {rejectionCount}, {isExecuting ? 'Executing' : 'Idle'}
    + {(state) => ( +
    Rejected: {state().rejectionCount}, {state().isExecuting ? 'Executing' : 'Idle'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md b/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md index 88f130b6..4ced62aa 100644 --- a/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md +++ b/docs/framework/solid/reference/interfaces/SolidAsyncThrottler.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ isPending: state.isPending, isExecuting: state.isExecuting })}> - {({ isPending, isExecuting }) => ( -
    {isPending ? 'Pending...' : isExecuting ? 'Executing...' : 'Ready'}
    + {(state) => ( +
    {state().isPending ? 'Pending...' : state().isExecuting ? 'Executing...' : 'Ready'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidBatcher.md b/docs/framework/solid/reference/interfaces/SolidBatcher.md index 6e2f94a0..7208c7a9 100644 --- a/docs/framework/solid/reference/interfaces/SolidBatcher.md +++ b/docs/framework/solid/reference/interfaces/SolidBatcher.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ size: state.size, isRunning: state.isRunning })}> - {({ size, isRunning }) => ( -
    Batch: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + {(state) => ( +
    Batch: {state().size} items, {state().isRunning ? 'Processing' : 'Idle'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidDebouncer.md b/docs/framework/solid/reference/interfaces/SolidDebouncer.md index 0302de26..da1b3000 100644 --- a/docs/framework/solid/reference/interfaces/SolidDebouncer.md +++ b/docs/framework/solid/reference/interfaces/SolidDebouncer.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ isPending: state.isPending })}> - {({ isPending }) => ( -
    {isPending ? 'Waiting...' : 'Ready'}
    + {(state) => ( +
    {state().isPending ? 'Waiting...' : 'Ready'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidQueuer.md b/docs/framework/solid/reference/interfaces/SolidQueuer.md index 0041cdf0..0f9a6d50 100644 --- a/docs/framework/solid/reference/interfaces/SolidQueuer.md +++ b/docs/framework/solid/reference/interfaces/SolidQueuer.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ size: state.size, isRunning: state.isRunning })}> - {({ size, isRunning }) => ( -
    Queue: {size} items, {isRunning ? 'Processing' : 'Idle'}
    + {(state) => ( +
    Queue: {state().size} items, {state().isRunning ? 'Processing' : 'Idle'}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidRateLimiter.md b/docs/framework/solid/reference/interfaces/SolidRateLimiter.md index 8f732e78..6c58e92b 100644 --- a/docs/framework/solid/reference/interfaces/SolidRateLimiter.md +++ b/docs/framework/solid/reference/interfaces/SolidRateLimiter.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ rejectionCount: state.rejectionCount })}> - {({ rejectionCount }) => ( -
    Rejections: {rejectionCount}
    + {(state) => ( +
    Rejections: {state().rejectionCount}
    )}
    ``` diff --git a/docs/framework/solid/reference/interfaces/SolidThrottler.md b/docs/framework/solid/reference/interfaces/SolidThrottler.md index f75e7c65..12a6b27e 100644 --- a/docs/framework/solid/reference/interfaces/SolidThrottler.md +++ b/docs/framework/solid/reference/interfaces/SolidThrottler.md @@ -93,8 +93,8 @@ deep in your component tree without needing to pass a selector to the hook. ```ts ({ isPending: state.isPending })}> - {({ isPending }) => ( -
    {isPending ? 'Loading...' : 'Ready'}
    + {(state) => ( +
    {state().isPending ? 'Loading...' : 'Ready'}
    )}
    ```