TanStack

Svelte Example: CreateAsyncDebouncer

<script lang="ts">
  import { TanStackDevtools } from '@tanstack/svelte-devtools'
  import { pacerDevtoolsPlugin } from '@tanstack/svelte-pacer-devtools'
  import { createAsyncDebouncer } from '@tanstack/svelte-pacer/async-debouncer'

  interface SearchResult {
    id: number
    title: string
  }
  // Simulate API call with fake data
  const fakeApi = async (term: string): Promise<Array<SearchResult>> => {
    await new Promise((resolve) => setTimeout(resolve, 1500)) // Simulate network delay
    return [
      { id: 1, title: `${term} result ${Math.floor(Math.random() * 100)}` },
      { id: 2, title: `${term} result ${Math.floor(Math.random() * 100)}` },
      { id: 3, title: `${term} result ${Math.floor(Math.random() * 100)}` },
    ]
  }
  let searchTerm = $state('')

  let results = $state<Array<SearchResult>>([])

  // The function that will become debounced
  const handleSearch = async (term: string) => {
    if (!term) {
      results = []
      return
    }
    // throw new Error('Test error') // you don't have to catch errors here (though you still can). The onError optional handler will catch it
    const data = await fakeApi(term)
    results = data
    return data // this could alternatively be a void function without a return
  }

  const asyncDebouncer = createAsyncDebouncer(handleSearch, () => ({
    key: 'createAsyncDebouncer',
    // leading: true, // optional leading execution
    wait: 500, // Wait 500ms between API calls
    onError: (error) => {
      // optional error handler
      console.error('Search failed:', error)
      results = []
    },
    // throwOnError: true,
    asyncRetryerOptions: {
      maxAttempts: 3,
      maxExecutionTime: 3000,
    },
  }))

  // get and name our debounced function
  const handleSearchDebounced = asyncDebouncer.maybeExecute

  // instant event handler that calls both the instant local state setter and the debounced function
  async function onSearchChange(e: Event) {
    const newTerm = (e.target as HTMLInputElement).value
    searchTerm = newTerm
    const result = await handleSearchDebounced(newTerm) // optionally await result if you need to
    console.log('result', result)
  }
</script>

<div>
  <h1>TanStack Pacer createAsyncDebouncer Example</h1>
  <div>
    <!-- svelte-ignore a11y_autofocus: Focus the search field in this standalone demo. -->
    <input
      autofocus
      type="search"
      value={searchTerm}
      oninput={onSearchChange}
      placeholder="Type to search..."
      style="width: 100%"
      autocomplete="new-password"
    />
  </div>
  <div style="margin-top: 10px">
    <button onclick={() => asyncDebouncer.flush()}>Flush</button>
  </div>
  <asyncDebouncer.Subscribe
    selector={(state) => ({
      isExecuting: state.isExecuting,
      isPending: state.isPending,
      successCount: state.successCount,
    })}
    >{#snippet children({ isExecuting, isPending, successCount })}<div>
        <p>API calls made: {successCount}</p>
        {#if results.length > 0}<ul>
            {#each results as item, index (index)}<li>{item.title}</li>{/each}
          </ul>{/if}{#if isPending}<p>Pending...</p>{/if}{#if isExecuting}<p>
            Executing...
          </p>{/if}
      </div>{/snippet}</asyncDebouncer.Subscribe
  ><asyncDebouncer.Subscribe selector={(state) => state}
    >{#snippet children(state)}<pre style="margin-top: 20px">{JSON.stringify(
          state,
          null,
          2,
        )}</pre>{/snippet}</asyncDebouncer.Subscribe
  >
</div>
{#if import.meta.env.DEV}<TanStackDevtools
    plugins={[pacerDevtoolsPlugin()]}
  />{/if}