debounce
debounce collects rapid update requests and applies them after a wait window. It is useful for text inputs, resize-driven values, or other high-frequency updates where intermediate states do not need to notify immediately.
Signature
debounce(wait?: number): PipeAnyMiddlewarewait is measured in milliseconds and defaults to 300. When supplied, it must be a finite non-negative number; 0 is valid. Invalid waits throw RangeError before middleware or timer setup.
Example
import { debounce } from '@ilokesto/state/middleware';
import { pipe } from '@ilokesto/state/utils';
const store = pipe
.use(debounce(250))
.create({ query: '' });
store.setState({ query: 'i' });
store.setState({ query: 'il' });
store.setState({ query: 'ilo' });Using with persist
Declare debounce before persist. Persistence observes the actual delayed commit, not the initial update request:
import { debounce, jsonStorage, persist } from '@ilokesto/state/middleware';
import { pipe } from '@ilokesto/state/utils';
type SearchState = { readonly query: string };
const decodeSearch = (value: unknown): SearchState | null => {
if (typeof value !== 'object' || value === null || !('query' in value)) return null;
return typeof value.query === 'string' ? { query: value.query } : null;
};
const store = pipe
.use(debounce(250))
.use(persist({ key: 'search', storage: jsonStorage(() => window.localStorage), decode: decodeSearch }))
.create<SearchState>({ query: '' });
await store.persist.rehydrate();The reverse declaration order remains rejected with MIDDLEWARE_ORDER. Restoration commits immediately without waiting for debounce. flush() waits for committed save targets; it does not force a pending debounce timer to run. The restriction on history() with debounce() or throttle() remains.
Function updates
Function updates are kept in order and replayed against an accumulated current state when the timer fires. That means updater functions still compose with one another inside the debounce window.
Timing caveat
Until the timer fires, getState() still returns the previous committed state. Do not use debounce for state where every intermediate write must be immediately observable. history() cannot share this pipe chain because history requires synchronous commits.
Cleanup
debounce owns its pending timer. When the store is no longer needed, call dispose(store) from @ilokesto/state/middleware to cancel it. Disposal is scoped to that store and is safe to repeat. It still attempts every registered cleanup after a failure, then throws an AggregateError containing each original thrown value when any cleanup fails.