| name | tanstack-pacer |
| description | | Use when this capability is needed. |
TanStack Pacer
Version: @tanstack/react-pacer@latest
Requires: React 16.8+, TypeScript recommended
Quick Setup
npm install @tanstack/react-pacer
import { useDebouncedCallback } from '@tanstack/react-pacer'
function SearchInput() {
const debouncedSearch = useDebouncedCallback(
(query: string) => fetchResults(query),
{ wait: 300 },
)
return <input onChange={(e) => debouncedSearch(e.target.value)} />
}
4 Hook Variants Per Utility
Each utility (Debouncer, Throttler, RateLimiter, Queuer, Batcher) provides 4 React hooks:
| Hook | Returns | Use Case |
|---|
use[Utility] | Instance | Full control, custom state subscriptions |
use[Utility]Callback | Wrapped function | Simple event handler wrapping |
use[Utility]State | [value, setValue, instance] | Debounced/throttled React state |
use[Utility]Value | [derivedValue, instance] | Read-only derived value from props/state |
PacerProvider (Optional)
import { PacerProvider } from '@tanstack/react-pacer'
<PacerProvider
defaultOptions={{
debouncer: { wait: 300 },
throttler: { wait: 200 },
}}
>
<App />
</PacerProvider>
Devtools
npm install -D @tanstack/react-devtools @tanstack/react-pacer-devtools
import { TanStackDevtools } from '@tanstack/react-devtools'
import { pacerDevtoolsPlugin } from '@tanstack/react-pacer-devtools'
<TanStackDevtools plugins={[pacerDevtoolsPlugin()]} />
Utilities must have a key option to appear in devtools.
Rule Categories
| Priority | Category | Rule File | Impact |
|---|
| CRITICAL | Hook Selection | rules/hook-selection.md | Correct hook choice per use case |
| CRITICAL | Debouncing | rules/deb-debouncing.md | Prevents wasted API calls and flickering UI |
| HIGH | Throttling | rules/thr-throttling.md | Smooth, evenly-spaced execution |
| HIGH | State & Reactivity | rules/state-reactivity.md | Prevents unnecessary re-renders |
| HIGH | Async Patterns | rules/async-patterns.md | Correct async execution with retry, abort, error handling |
| MEDIUM | Rate Limiting | rules/rl-rate-limiting.md | Enforces execution budgets |
| MEDIUM | Queuing | rules/que-queuing.md | Lossless ordered/priority task processing |
| MEDIUM | Batching | rules/bat-batching.md | Groups operations for bulk processing |
| LOW | Configuration | rules/config-options.md | Dynamic options, providers, shared config |
| LOW | Devtools | rules/devtools.md | Debugging and monitoring utilities |
Critical Rules
Always Do
- Use hooks over function wrappers —
useDebouncedCallback not debounce() for proper React lifecycle
- Choose the right hook variant —
useCallback for event handlers, useState for controlled inputs, useValue for derived values
- Opt-in to state subscriptions — pass selector as 3rd arg:
useDebouncer(fn, opts, (s) => ({ isPending: s.isPending }))
- Use async variants for API calls —
useAsyncDebouncedCallback gives error handling, retry, abort
- Pass
AbortSignal to fetch — getAbortSignal() enables cancellation of in-flight requests
- Use
key option for devtools — only keyed utilities appear in devtools panel
Never Do
- Subscribe to all state — omit selector or use instance directly to avoid re-renders on every state change
- Use debouncing when you need guaranteed execution — use throttling or queuing instead
- Use rate limiting for evenly-spaced calls — rate limiting is bursty; use throttling for smooth spacing
- Use
debounce() function in React — no lifecycle cleanup; use useDebouncedCallback hook instead
- Expect
maxWait on Debouncer — Pacer has no maxWait; use Throttler for guaranteed periodic execution
Key Patterns
import { useDebouncedState } from '@tanstack/react-pacer'
function Search() {
const [query, setQuery, debouncer] = useDebouncedState('', { wait: 300 })
return <input onChange={(e) => setQuery(e.target.value)} />
}
import { useAsyncDebouncedCallback } from '@tanstack/react-pacer'
function AsyncSearch() {
const search = useAsyncDebouncedCallback(
async (query: string) => {
const signal = search.getAbortSignal()
const res = await fetch(`/api/search?q=${query}`, { signal })
return res.json()
},
{ wait: 300, onSuccess: (data) => setResults(data) },
)
return <input onChange= => search(e.target.value)} />
}
{ useThrottledCallback }
() {
onScroll = (
(.),
{ : },
)
( {
.(, onScroll)
.(, onScroll)
}, [onScroll])
}
{ useDebouncedValue }
() {
[debouncedFilter] = (filter, { : })
}
{ useAsyncQueuer }
() {
queuer = (uploadFile, { : })
}
Converted and distributed by TomeVault — claim your Tome and manage your conversions.