ilokesto

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): PipeAnyMiddleware

wait 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.

On this page