Docs
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
CodeRabbit
Cloudflare
AG Grid
SerpAPI
Netlify
OpenRouter
Neon
WorkOS
Clerk
Electric
PowerSync
Sentry
Railway
Prisma
Strapi
Unkey
Debouncer API Reference
Throttler API Reference
Rate Limiter API Reference
Queue API Reference
Batcher API Reference

Lit Example: CreateDebouncer

import { LitElement, html } from 'lit'
import { createDebouncer } from '@tanstack/lit-pacer'
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { pacerDevtoolsPlugin } from '@tanstack/pacer-devtools'

class Counter extends LitElement {
  static properties = {
    instantCount: { state: true },
    debouncedCount: { state: true },
  }
  instantCount = 0
  debouncedCount = 0
  debouncer = createDebouncer(
    this,
    (value: number) => {
      this.debouncedCount = value
    },
    {
      key: 'counter',
      wait: 800,
      enabled: () => this.instantCount > 2,
    },
    (state) => state,
  )
  increment = () => {
    this.instantCount++
    this.debouncer.maybeExecute(this.instantCount)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 1</h1>
      <table>
        <tbody>
          <tr>
            <td>Status:</td>
            <td>${this.debouncer.state.status}</td>
          </tr>
          <tr>
            <td>Execution Count:</td>
            <td>${this.debouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td colspan=${2}><hr /></td>
          </tr>
          <tr>
            <td>Instant Count:</td>
            <td>${this.instantCount}</td>
          </tr>
          <tr>
            <td>Debounced Count:</td>
            <td>${this.debouncedCount}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button @click=${this.increment}>Increment</button
        ><button
          @click=${() => this.debouncer.flush()}
          style="margin-left: 10px"
        >
          Flush
        </button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.debouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-counter', Counter)

class Search extends LitElement {
  static properties = {
    searchText: { state: true },
    debouncedSearchText: { state: true },
  }
  searchText = ''
  debouncedSearchText = ''
  setSearchDebouncer = createDebouncer(
    this,
    (value: string) => {
      this.debouncedSearchText = value
    },
    {
      key: 'search',
      wait: 500,
      enabled: () => this.searchText.length > 2,
    },
    (state) => state,
  )
  handleSearchChange = (event: Event) => {
    this.searchText = (event.target as HTMLInputElement).value
    this.setSearchDebouncer.maybeExecute(this.searchText)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 2</h1>
      <div>
        <input
          autofocus
          type="search"
          .value=${this.searchText}
          @input=${this.handleSearchChange}
          placeholder="Type to search..."
          style="width: 100%; margin-bottom: 1rem"
        />
      </div>
      <table>
        <tbody>
          <tr>
            <td>Is Pending:</td>
            <td>${this.setSearchDebouncer.state.isPending.toString()}</td>
          </tr>
          <tr>
            <td>Execution Count:</td>
            <td>${this.setSearchDebouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td colspan=${2}><hr /></td>
          </tr>
          <tr>
            <td>Instant Search:</td>
            <td>${this.searchText}</td>
          </tr>
          <tr>
            <td>Debounced Search:</td>
            <td>${this.debouncedSearchText}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button @click=${() => this.setSearchDebouncer.flush()}>Flush</button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.setSearchDebouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-search', Search)

class Range extends LitElement {
  static properties = {
    currentValue: { state: true },
    debouncedValue: { state: true },
    instantExecutionCount: { state: true },
    wait: { state: true },
    enabled: { state: true },
  }
  currentValue = 50
  debouncedValue = 50
  instantExecutionCount = 0
  wait = 250
  enabled = true
  setValueDebouncer = createDebouncer(
    this,
    (value: number) => {
      this.debouncedValue = value
    },
    () => ({ key: 'range', wait: this.wait, enabled: this.enabled }),
    (state) => state,
  )
  handleRangeChange = (event: Event) => {
    this.currentValue = Number((event.target as HTMLInputElement).value)
    this.instantExecutionCount++
    this.setValueDebouncer.maybeExecute(this.currentValue)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 3</h1>
      <fieldset>
        <legend>Reactive options</legend>
        <label
          >Delay: ${this.wait} ms<input
            type="range"
            min="0"
            max="1500"
            step="50"
            .value=${this.wait}
            @input=${(event: Event) => {
              this.wait = (
                event.currentTarget as HTMLInputElement
              ).valueAsNumber
            }} /></label
        ><label
          ><input
            type="checkbox"
            .checked=${this.enabled}
            @input=${(event: Event) => {
              this.enabled = (event.currentTarget as HTMLInputElement).checked
            }}
          />Enabled</label
        >
        <p>
          Changing the delay affects the next scheduled call. Disabling cancels
          pending work.
        </p>
      </fieldset>
      <div style="margin-bottom: 20px">
        <label
          >Current Range:<input
            type="range"
            min="0"
            max="100"
            .value=${this.currentValue}
            @input=${this.handleRangeChange}
            style="width: 100%"
          /><span>${this.currentValue}</span></label
        >
      </div>
      <div style="margin-bottom: 20px">
        <label
          >Debounced Range (Readonly):<input
            type="range"
            min="0"
            max="100"
            .value=${this.debouncedValue}
            disabled
            style="width: 100%"
          /><span>${this.debouncedValue}</span></label
        >
      </div>
      <table>
        <tbody>
          <tr>
            <td>Is Pending:</td>
            <td>${this.setValueDebouncer.state.isPending.toString()}</td>
          </tr>
          <tr>
            <td>Instant Executions:</td>
            <td>${this.instantExecutionCount}</td>
          </tr>
          <tr>
            <td>Debounced Executions:</td>
            <td>${this.setValueDebouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td>Saved Executions:</td>
            <td>
              ${this.instantExecutionCount - this.setValueDebouncer.state.executionCount}
            </td>
          </tr>
          <tr>
            <td>% Reduction:</td>
            <td>
              ${
                this.instantExecutionCount === 0
                  ? '0'
                  : Math.round(
                      ((this.instantExecutionCount -
                        this.setValueDebouncer.state.executionCount) /
                        this.instantExecutionCount) *
                        100,
                    )
              }%
            </td>
          </tr>
        </tbody>
      </table>
      <div style="color: #666; font-size: 0.9em">
        <p>Debounced to ${this.wait}ms wait time</p>
      </div>
      <div>
        <button @click=${() => this.setValueDebouncer.flush()}>Flush</button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.setValueDebouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-range', Range)

class Example extends LitElement {
  private devtools?: TanStackDevtoolsCore
  private target?: HTMLDivElement
  override createRenderRoot() {
    return this
  }
  override connectedCallback() {
    super.connectedCallback()
    if (!import.meta.env.DEV) return
    this.target = document.createElement('div')
    document.body.append(this.target)
    this.devtools = new TanStackDevtoolsCore({
      plugins: [pacerDevtoolsPlugin()],
    })
    this.devtools.mount(this.target)
  }
  override disconnectedCallback() {
    this.devtools?.unmount()
    this.target?.remove()
    this.devtools = undefined
    this.target = undefined
    super.disconnectedCallback()
  }
  override render() {
    return html`<div>
      <pacer-counter></pacer-counter>
      <hr />
      <pacer-search></pacer-search>
      <hr />
      <pacer-range></pacer-range>
    </div>`
  }
}
customElements.define('pacer-example', Example)
document.getElementById('app')!.append(document.createElement('pacer-example'))
import { LitElement, html } from 'lit'
import { createDebouncer } from '@tanstack/lit-pacer'
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { pacerDevtoolsPlugin } from '@tanstack/pacer-devtools'

class Counter extends LitElement {
  static properties = {
    instantCount: { state: true },
    debouncedCount: { state: true },
  }
  instantCount = 0
  debouncedCount = 0
  debouncer = createDebouncer(
    this,
    (value: number) => {
      this.debouncedCount = value
    },
    {
      key: 'counter',
      wait: 800,
      enabled: () => this.instantCount > 2,
    },
    (state) => state,
  )
  increment = () => {
    this.instantCount++
    this.debouncer.maybeExecute(this.instantCount)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 1</h1>
      <table>
        <tbody>
          <tr>
            <td>Status:</td>
            <td>${this.debouncer.state.status}</td>
          </tr>
          <tr>
            <td>Execution Count:</td>
            <td>${this.debouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td colspan=${2}><hr /></td>
          </tr>
          <tr>
            <td>Instant Count:</td>
            <td>${this.instantCount}</td>
          </tr>
          <tr>
            <td>Debounced Count:</td>
            <td>${this.debouncedCount}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button @click=${this.increment}>Increment</button
        ><button
          @click=${() => this.debouncer.flush()}
          style="margin-left: 10px"
        >
          Flush
        </button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.debouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-counter', Counter)

class Search extends LitElement {
  static properties = {
    searchText: { state: true },
    debouncedSearchText: { state: true },
  }
  searchText = ''
  debouncedSearchText = ''
  setSearchDebouncer = createDebouncer(
    this,
    (value: string) => {
      this.debouncedSearchText = value
    },
    {
      key: 'search',
      wait: 500,
      enabled: () => this.searchText.length > 2,
    },
    (state) => state,
  )
  handleSearchChange = (event: Event) => {
    this.searchText = (event.target as HTMLInputElement).value
    this.setSearchDebouncer.maybeExecute(this.searchText)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 2</h1>
      <div>
        <input
          autofocus
          type="search"
          .value=${this.searchText}
          @input=${this.handleSearchChange}
          placeholder="Type to search..."
          style="width: 100%; margin-bottom: 1rem"
        />
      </div>
      <table>
        <tbody>
          <tr>
            <td>Is Pending:</td>
            <td>${this.setSearchDebouncer.state.isPending.toString()}</td>
          </tr>
          <tr>
            <td>Execution Count:</td>
            <td>${this.setSearchDebouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td colspan=${2}><hr /></td>
          </tr>
          <tr>
            <td>Instant Search:</td>
            <td>${this.searchText}</td>
          </tr>
          <tr>
            <td>Debounced Search:</td>
            <td>${this.debouncedSearchText}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button @click=${() => this.setSearchDebouncer.flush()}>Flush</button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.setSearchDebouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-search', Search)

class Range extends LitElement {
  static properties = {
    currentValue: { state: true },
    debouncedValue: { state: true },
    instantExecutionCount: { state: true },
    wait: { state: true },
    enabled: { state: true },
  }
  currentValue = 50
  debouncedValue = 50
  instantExecutionCount = 0
  wait = 250
  enabled = true
  setValueDebouncer = createDebouncer(
    this,
    (value: number) => {
      this.debouncedValue = value
    },
    () => ({ key: 'range', wait: this.wait, enabled: this.enabled }),
    (state) => state,
  )
  handleRangeChange = (event: Event) => {
    this.currentValue = Number((event.target as HTMLInputElement).value)
    this.instantExecutionCount++
    this.setValueDebouncer.maybeExecute(this.currentValue)
  }

  override createRenderRoot() {
    return this
  }
  override render() {
    return html`<div>
      <h1>TanStack Pacer createDebouncer Example 3</h1>
      <fieldset>
        <legend>Reactive options</legend>
        <label
          >Delay: ${this.wait} ms<input
            type="range"
            min="0"
            max="1500"
            step="50"
            .value=${this.wait}
            @input=${(event: Event) => {
              this.wait = (
                event.currentTarget as HTMLInputElement
              ).valueAsNumber
            }} /></label
        ><label
          ><input
            type="checkbox"
            .checked=${this.enabled}
            @input=${(event: Event) => {
              this.enabled = (event.currentTarget as HTMLInputElement).checked
            }}
          />Enabled</label
        >
        <p>
          Changing the delay affects the next scheduled call. Disabling cancels
          pending work.
        </p>
      </fieldset>
      <div style="margin-bottom: 20px">
        <label
          >Current Range:<input
            type="range"
            min="0"
            max="100"
            .value=${this.currentValue}
            @input=${this.handleRangeChange}
            style="width: 100%"
          /><span>${this.currentValue}</span></label
        >
      </div>
      <div style="margin-bottom: 20px">
        <label
          >Debounced Range (Readonly):<input
            type="range"
            min="0"
            max="100"
            .value=${this.debouncedValue}
            disabled
            style="width: 100%"
          /><span>${this.debouncedValue}</span></label
        >
      </div>
      <table>
        <tbody>
          <tr>
            <td>Is Pending:</td>
            <td>${this.setValueDebouncer.state.isPending.toString()}</td>
          </tr>
          <tr>
            <td>Instant Executions:</td>
            <td>${this.instantExecutionCount}</td>
          </tr>
          <tr>
            <td>Debounced Executions:</td>
            <td>${this.setValueDebouncer.state.executionCount}</td>
          </tr>
          <tr>
            <td>Saved Executions:</td>
            <td>
              ${this.instantExecutionCount - this.setValueDebouncer.state.executionCount}
            </td>
          </tr>
          <tr>
            <td>% Reduction:</td>
            <td>
              ${
                this.instantExecutionCount === 0
                  ? '0'
                  : Math.round(
                      ((this.instantExecutionCount -
                        this.setValueDebouncer.state.executionCount) /
                        this.instantExecutionCount) *
                        100,
                    )
              }%
            </td>
          </tr>
        </tbody>
      </table>
      <div style="color: #666; font-size: 0.9em">
        <p>Debounced to ${this.wait}ms wait time</p>
      </div>
      <div>
        <button @click=${() => this.setValueDebouncer.flush()}>Flush</button>
      </div>
      <pre style="margin-top: 20px">
${JSON.stringify(this.setValueDebouncer.state, null, 2)}</pre>
    </div>`
  }
}
customElements.define('pacer-range', Range)

class Example extends LitElement {
  private devtools?: TanStackDevtoolsCore
  private target?: HTMLDivElement
  override createRenderRoot() {
    return this
  }
  override connectedCallback() {
    super.connectedCallback()
    if (!import.meta.env.DEV) return
    this.target = document.createElement('div')
    document.body.append(this.target)
    this.devtools = new TanStackDevtoolsCore({
      plugins: [pacerDevtoolsPlugin()],
    })
    this.devtools.mount(this.target)
  }
  override disconnectedCallback() {
    this.devtools?.unmount()
    this.target?.remove()
    this.devtools = undefined
    this.target = undefined
    super.disconnectedCallback()
  }
  override render() {
    return html`<div>
      <pacer-counter></pacer-counter>
      <hr />
      <pacer-search></pacer-search>
      <hr />
      <pacer-range></pacer-range>
    </div>`
  }
}
customElements.define('pacer-example', Example)
document.getElementById('app')!.append(document.createElement('pacer-example'))