If you are using TanStack Pacer in a React application, we recommend using the React Adapter. The React 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 React Adapter will also re-export everything from the core package.
npm install @tanstack/react-pacer
npm install @tanstack/react-pacer
See the React Functions Reference to see the full list of hooks available in the React Adapter.
Import a react specific hook from the React Adapter.
import { useDebouncedValue } from '@tanstack/react-pacer'
const [instantValue, instantValueRef] = useState(0)
const [debouncedValue, debouncer] = useDebouncedValue(instantValue, {
wait: 1000,
})
import { useDebouncedValue } from '@tanstack/react-pacer'
const [instantValue, instantValueRef] = useState(0)
const [debouncedValue, debouncer] = useDebouncedValue(instantValue, {
wait: 1000,
})
Or import a core Pacer class/function that is re-exported from the React Adapter.
import { debounce, Debouncer } from '@tanstack/react-pacer' // no need to install the core package separately
import { debounce, Debouncer } from '@tanstack/react-pacer' // no need to install the core package separately
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 React hooks.
import { useDebouncer } from '@tanstack/react-pacer'
import { debouncerOptions } from '@tanstack/pacer'
const commonDebouncerOptions = debouncerOptions({
wait: 1000,
leading: false,
trailing: true,
})
const debouncer = useDebouncer(
(query: string) => fetchSearchResults(query),
{ ...commonDebouncerOptions, key: 'searchDebouncer' }
)
import { useDebouncer } from '@tanstack/react-pacer'
import { debouncerOptions } from '@tanstack/pacer'
const commonDebouncerOptions = debouncerOptions({
wait: 1000,
leading: false,
trailing: true,
})
const debouncer = useDebouncer(
(query: string) => fetchSearchResults(query),
{ ...commonDebouncerOptions, key: 'searchDebouncer' }
)
import { useQueuer } from '@tanstack/react-pacer'
import { queuerOptions } from '@tanstack/pacer'
const commonQueuerOptions = queuerOptions({
concurrency: 3,
addItemsTo: 'back',
})
const queuer = useQueuer(
(item: string) => processItem(item),
{ ...commonQueuerOptions, key: 'itemQueuer' }
)
import { useQueuer } from '@tanstack/react-pacer'
import { queuerOptions } from '@tanstack/pacer'
const commonQueuerOptions = queuerOptions({
concurrency: 3,
addItemsTo: 'back',
})
const queuer = useQueuer(
(item: string) => processItem(item),
{ ...commonQueuerOptions, key: 'itemQueuer' }
)
import { useRateLimiter } from '@tanstack/react-pacer'
import { rateLimiterOptions } from '@tanstack/pacer'
const commonRateLimiterOptions = rateLimiterOptions({
limit: 5,
window: 60000,
windowType: 'sliding',
})
const rateLimiter = useRateLimiter(
(data: string) => sendApiRequest(data),
{ ...commonRateLimiterOptions, key: 'apiRateLimiter' }
)
import { useRateLimiter } from '@tanstack/react-pacer'
import { rateLimiterOptions } from '@tanstack/pacer'
const commonRateLimiterOptions = rateLimiterOptions({
limit: 5,
window: 60000,
windowType: 'sliding',
})
const rateLimiter = useRateLimiter(
(data: string) => sendApiRequest(data),
{ ...commonRateLimiterOptions, key: 'apiRateLimiter' }
)
The React Adapter provides a PacerProvider component that you can use to provide default options to all instances of pacer utilities within your component tree.
import { PacerProvider } from '@tanstack/react-pacer'
// Set default options for react-pacer instances
<PacerProvider
defaultOptions={{
debouncer: { wait: 1000 },
queuer: { concurrency: 3 },
rateLimiter: { limit: 5, window: 60000 },
}}
>
<App />
</PacerProvider>
import { PacerProvider } from '@tanstack/react-pacer'
// Set default options for react-pacer instances
<PacerProvider
defaultOptions={{
debouncer: { wait: 1000 },
queuer: { concurrency: 3 },
rateLimiter: { limit: 5, window: 60000 },
}}
>
<App />
</PacerProvider>
All hooks within the provider will automatically use these default options, which can be overridden on a per-hook basis.
The React adapter supports subscribing to state changes in two ways:
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.
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 (
<div>
<button onClick={() => rateLimiter.maybeExecute('some data')}>
Submit
</button>
<rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
{({ rejectionCount }) => (
<div>Rejections: {rejectionCount}</div>
)}
</rateLimiter.Subscribe>
</div>
)
}
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 (
<div>
<button onClick={() => rateLimiter.maybeExecute('some data')}>
Submit
</button>
<rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
{({ rejectionCount }) => (
<div>Rejections: {rejectionCount}</div>
)}
</rateLimiter.Subscribe>
</div>
)
}
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.
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 (
<input
onChange={(e) => debouncer.maybeExecute(e.target.value)}
placeholder="Search..."
/>
)
}
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 (
<input
onChange={(e) => 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, Debouncing Guide).
import { useDebouncer } from '@tanstack/react-pacer'
function SearchComponent() {
const debouncer = useDebouncer(
(query: string) => {
console.log('Searching for:', query)
// Perform search
},
{ wait: 500 }
)
return (
<input
onChange={(e) => debouncer.maybeExecute(e.target.value)}
placeholder="Search..."
/>
)
}
import { useDebouncer } from '@tanstack/react-pacer'
function SearchComponent() {
const debouncer = useDebouncer(
(query: string) => {
console.log('Searching for:', query)
// Perform search
},
{ wait: 500 }
)
return (
<input
onChange={(e) => debouncer.maybeExecute(e.target.value)}
placeholder="Search..."
/>
)
}
import { useQueuer } from '@tanstack/react-pacer'
function UploadComponent() {
const queuer = useQueuer(
async (file: File) => {
await uploadFile(file)
},
{ concurrency: 3 }
)
const handleFileSelect = (files: FileList) => {
Array.from(files).forEach((file) => {
queuer.add(file)
})
}
return (
<input
type="file"
multiple
onChange={(e) => {
if (e.target.files) {
handleFileSelect(e.target.files)
}
}}
/>
)
}
import { useQueuer } from '@tanstack/react-pacer'
function UploadComponent() {
const queuer = useQueuer(
async (file: File) => {
await uploadFile(file)
},
{ concurrency: 3 }
)
const handleFileSelect = (files: FileList) => {
Array.from(files).forEach((file) => {
queuer.add(file)
})
}
return (
<input
type="file"
multiple
onChange={(e) => {
if (e.target.files) {
handleFileSelect(e.target.files)
}
}}
/>
)
}
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,
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 <button onClick={handleSubmit}>Submit</button>
}
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,
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 <button onClick={handleSubmit}>Submit</button>
}
