In a Solid application, use the Solid Adapter. Its hooks wrap the core Pacer utilities with lifecycle cleanup and reactive state. The adapter also re-exports everything from the core package, so you can import the plain classes and functions from the same place.
npm install @tanstack/solid-pacerSee the Solid Functions Reference for the full list of hooks in the Solid Adapter.
Import a Solid-specific hook from the Solid Adapter.
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.
import { debounce, Debouncer } from '@tanstack/solid-pacer' // no need to install the core package separatelyOption helpers define shared options with full type checking, so you can declare them once and reuse them across hooks.
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' }
)import { createAsyncQueuer } from '@tanstack/solid-pacer'
import { asyncQueuerOptions } from '@tanstack/pacer'
const commonAsyncQueuerOptions = asyncQueuerOptions({
concurrency: 3,
addItemsTo: 'back',
})
const queuer = createAsyncQueuer(
async (item: string) => processItem(item),
{ ...commonAsyncQueuerOptions, key: 'itemQueuer' }
)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' }
)The PacerProvider component sets default options for every Pacer utility instance in its component tree.
import { PacerProvider } from '@tanstack/solid-pacer'
// Set default options for solid-pacer instances
<PacerProvider
defaultOptions={{
debouncer: { wait: 1000 },
asyncQueuer: { concurrency: 3 },
rateLimiter: { limit: 5, window: 60000 },
}}
>
<App />
</PacerProvider>Hooks inside the provider use these defaults. Options passed to an individual hook override them.
The Solid Adapter supports subscribing to state changes in two ways:
Use the Subscribe component to read state deep in the component tree without passing a selector to the hook.
In Solid, the Subscribe component provides an accessor (signal) to the selected state, so call state() to read the value.
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 (
<div>
<button onClick={() => rateLimiter.maybeExecute('some data')}>
Submit
</button>
<rateLimiter.Subscribe selector={(state) => ({ rejectionCount: state.rejectionCount })}>
{(state) => (
<div>Rejections: {state().rejectionCount}</div>
)}
</rateLimiter.Subscribe>
</div>
)
}The selector parameter controls which state changes trigger reactive updates. State you do not select never causes an update.
Without a selector, hook.state is an empty object ({}). Pass a selector function to opt in to state tracking.
In Solid, hook.state is an accessor (signal), so call hook.state() to read the value.
import { createDebouncer } from '@tanstack/solid-pacer'
function SearchComponent() {
// Default behavior - no reactive state subscriptions
const untrackedDebouncer = createDebouncer(
(query: string) => fetchSearchResults(query),
{ wait: 500 }
)
console.log(untrackedDebouncer.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 (
<input
onInput={(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 { createDebouncer } from '@tanstack/solid-pacer'
function SearchComponent() {
const debouncer = createDebouncer(
(query: string) => {
console.log('Searching for:', query)
// Perform search
},
{ wait: 500 }
)
return (
<input
onInput={(e) => debouncer.maybeExecute(e.currentTarget.value)}
placeholder="Search..."
/>
)
}import { createAsyncQueuer } from '@tanstack/solid-pacer'
function UploadComponent() {
const queuer = createAsyncQueuer(
async (file: File) => {
await uploadFile(file)
},
{ concurrency: 3 }
)
const handleFileSelect = (files: FileList) => {
Array.from(files).forEach((file) => {
queuer.addItem(file)
})
}
return (
<input
type="file"
multiple
onChange={(e) => {
if (e.target.files) {
handleFileSelect(e.target.files)
}
}}
/>
)
}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 <button onClick={handleSubmit}>Submit</button>
}Use property getters to read Solid signals or props in an options object:
const [wait, setWait] = createSignal(300)
const debouncer = createDebouncer(save, {
get wait() {
return wait()
},
})
setWait(600)An options accessor supports the same behavior:
const debouncer = createDebouncer(save, () => ({ wait: wait() }))The adapter reads getters and accessors in a reactive computation. It initializes the utility immediately, then updates the same utility through setOptions when a dependency changes. This applies to all synchronous and asynchronous create functions and their signal and value helpers. Provider defaults are read in the same computation, and local options override them.
Reading a signal before passing the options, such as { wait: wait() }, produces a snapshot. Assigning to an ordinary object property does not trigger an update. Use a getter or accessor for reactive values. Option reads are shallow: to track a nested configuration, build that configuration inside a getter or accessor. Callbacks and function-valued core options remain functions and are not invoked when options are resolved.
Updates preserve the utility, store, pending work, and queued items. Changing wait does not reschedule an existing timer. Setting enabled to false still applies the utility's normal cancellation behavior. Construction options such as key, initialState, and initialItems apply only when the utility is created. Use start() and stop() to change running queues.
Updates follow setOptions merge semantics. If an accessor omits a previously supplied field, the provider default replaces it when one exists; otherwise, its previous value remains. Return undefined explicitly to clear an optional field. For example, onUnmount: undefined restores default cleanup. Disposal uses the latest onUnmount callback. The utility's options property exposes its current core options, including manual setOptions updates.