Auto
Documentation
Debouncer API Reference
Throttler API Reference
Rate Limiter API Reference
Queue API Reference
Batcher API Reference

Preact Example: UseAsyncThrottledCallback

tsx
import { useState } from 'preact/hooks'
import { render } from 'preact'
import type { JSX } from 'preact'
import { useAsyncThrottledCallback } from '@tanstack/preact-pacer/async-throttler'

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, 500)) // Simulate network delay
  if (term === 'error') {
    throw new Error('Simulated API error')
  }
  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)}` },
  ]
}

function App1() {
  const [searchTerm, setSearchTerm] = useState('')
  const [results, setResults] = useState<Array<SearchResult>>([])
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  // Create async throttled function - Stable reference provided by useAsyncThrottledCallback
  const throttledSearch = useAsyncThrottledCallback(
    async (term: string) => {
      if (!term.trim()) {
        setResults([])
        return []
      }

      setIsLoading(true)
      setError(null)

      try {
        const data = await fakeApi(term)
        setResults(data)
        return data
      } catch (err) {
        const errorMessage =
          err instanceof Error ? err.message : 'Unknown error'
        setError(errorMessage)
        setResults([])
        throw err
      } finally {
        setIsLoading(false)
      }
    },
    {
      wait: 1000,
      // leading: true, // optional, defaults to true
      // trailing: true, // optional, defaults to true
    },
  )

  async function handleSearchChange(e: JSX.TargetedEvent<HTMLInputElement>) {
    const newValue = e.currentTarget.value
    setSearchTerm(newValue)

    try {
      await throttledSearch(newValue)
    } catch (err) {
      // Error is already handled in the throttled function
      console.log('Search failed:', err)
    }
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 1</h1>
      <div>
        <input
          autoFocus
          type="search"
          value={searchTerm}
          onInput={handleSearchChange}
          placeholder="Type to search... (try 'error' to see error handling)"
          style={{ width: '100%', marginBottom: '10px' }}
        />
      </div>

      {isLoading && <p style={{ color: 'blue' }}>Searching...</p>}
      {error && <p style={{ color: 'red' }}>Error: {error}</p>}

      <div>
        <p>Current search term: {searchTerm}</p>
        {results.length > 0 && (
          <div>
            <h3>Results:</h3>
            <ul>
              {results.map((result) => (
                <li key={result.id}>{result.title}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  )
}

function App2() {
  const [count, setCount] = useState(0)
  const [apiCallCount, setApiCallCount] = useState(0)

  // Simulate API call that returns a value
  const incrementApi = async (value: number): Promise<number> => {
    await new Promise((resolve) => setTimeout(resolve, 300))
    const newCount = value + 1
    setApiCallCount((prev) => prev + 1)
    return newCount
  }

  // Create async throttled increment function
  const throttledIncrement = useAsyncThrottledCallback(
    async (currentValue: number) => {
      const result = await incrementApi(currentValue)
      setCount(result)
      return result
    },
    {
      wait: 1000,
      leading: true, // Execute immediately on first call
      trailing: true, // Execute after throttle period ends
    },
  )

  function handleIncrement() {
    // Update local state immediately for instant feedback
    setCount((prev) => {
      const newCount = prev + 1
      throttledIncrement(newCount)
      return newCount
    })
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 2</h1>
      <table>
        <tbody>
          <tr>
            <td>Current Count:</td>
            <td>{count}</td>
          </tr>
          <tr>
            <td>API Calls Made:</td>
            <td>{apiCallCount}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button onClick={handleIncrement}>
          Increment (throttled API call)
        </button>
      </div>
      <p style={{ fontSize: '0.9em', color: '#666' }}>
        Click rapidly - API calls are throttled to 1 second, but UI updates
        immediately. First click executes immediately, then at most once per
        second.
      </p>
    </div>
  )
}

function App3() {
  const [scrollPosition, setScrollPosition] = useState(0)
  const [saveCount, setSaveCount] = useState(0)
  const [lastSaved, setLastSaved] = useState<Date | null>(null)
  const [isSaving, setIsSaving] = useState(false)

  // Simulate saving scroll position to server
  const saveScrollPosition = async (
    position: number,
  ): Promise<{ success: boolean; position: number }> => {
    await new Promise((resolve) => setTimeout(resolve, 300))
    return { success: true, position }
  }

  // Create throttled save function
  const throttledSave = useAsyncThrottledCallback(
    async (position: number) => {
      setIsSaving(true)

      try {
        const result = await saveScrollPosition(position)
        setSaveCount((prev) => prev + 1)
        setLastSaved(new Date())
        return result
      } finally {
        setIsSaving(false)
      }
    },
    {
      wait: 1000,
      leading: true,
      trailing: true,
    },
  )

  function handleScroll(e: JSX.TargetedEvent<HTMLDivElement>) {
    const position = e.currentTarget.scrollTop
    setScrollPosition(position)
    throttledSave(position)
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 3</h1>
      <div
        style={{
          height: '200px',
          overflow: 'auto',
          border: '1px solid #ccc',
          padding: '10px',
          marginBottom: '20px',
        }}
        onScroll={handleScroll}
      >
        <div style={{ height: '1000px' }}>
          <p>Scroll this area to trigger throttled saves!</p>
          <p>Current scroll position: {Math.round(scrollPosition)}px</p>
          {isSaving && <p style={{ color: 'blue' }}>Saving position...</p>}
          <div style={{ marginTop: '20px' }}>
            <p>Saves triggered: {saveCount}</p>
            {lastSaved && (
              <p>Last saved at: {lastSaved.toLocaleTimeString()}</p>
            )}
          </div>
          <div style={{ marginTop: '40px' }}>
            <p>Keep scrolling...</p>
            <p style={{ marginTop: '100px' }}>More content...</p>
            <p style={{ marginTop: '100px' }}>Even more content...</p>
            <p style={{ marginTop: '100px' }}>Almost there...</p>
            <p style={{ marginTop: '100px' }}>You made it to the end!</p>
          </div>
        </div>
      </div>

      <p style={{ fontSize: '0.9em', color: '#666' }}>
        Scroll position is saved at most once per second, but updates instantly
        on screen
      </p>
    </div>
  )
}

const root = document.getElementById('root')!
render(
  <div>
    <App1 />
    <hr />
    <App2 />
    <hr />
    <App3 />
  </div>,
  root,
)
import { useState } from 'preact/hooks'
import { render } from 'preact'
import type { JSX } from 'preact'
import { useAsyncThrottledCallback } from '@tanstack/preact-pacer/async-throttler'

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, 500)) // Simulate network delay
  if (term === 'error') {
    throw new Error('Simulated API error')
  }
  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)}` },
  ]
}

function App1() {
  const [searchTerm, setSearchTerm] = useState('')
  const [results, setResults] = useState<Array<SearchResult>>([])
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  // Create async throttled function - Stable reference provided by useAsyncThrottledCallback
  const throttledSearch = useAsyncThrottledCallback(
    async (term: string) => {
      if (!term.trim()) {
        setResults([])
        return []
      }

      setIsLoading(true)
      setError(null)

      try {
        const data = await fakeApi(term)
        setResults(data)
        return data
      } catch (err) {
        const errorMessage =
          err instanceof Error ? err.message : 'Unknown error'
        setError(errorMessage)
        setResults([])
        throw err
      } finally {
        setIsLoading(false)
      }
    },
    {
      wait: 1000,
      // leading: true, // optional, defaults to true
      // trailing: true, // optional, defaults to true
    },
  )

  async function handleSearchChange(e: JSX.TargetedEvent<HTMLInputElement>) {
    const newValue = e.currentTarget.value
    setSearchTerm(newValue)

    try {
      await throttledSearch(newValue)
    } catch (err) {
      // Error is already handled in the throttled function
      console.log('Search failed:', err)
    }
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 1</h1>
      <div>
        <input
          autoFocus
          type="search"
          value={searchTerm}
          onInput={handleSearchChange}
          placeholder="Type to search... (try 'error' to see error handling)"
          style={{ width: '100%', marginBottom: '10px' }}
        />
      </div>

      {isLoading && <p style={{ color: 'blue' }}>Searching...</p>}
      {error && <p style={{ color: 'red' }}>Error: {error}</p>}

      <div>
        <p>Current search term: {searchTerm}</p>
        {results.length > 0 && (
          <div>
            <h3>Results:</h3>
            <ul>
              {results.map((result) => (
                <li key={result.id}>{result.title}</li>
              ))}
            </ul>
          </div>
        )}
      </div>
    </div>
  )
}

function App2() {
  const [count, setCount] = useState(0)
  const [apiCallCount, setApiCallCount] = useState(0)

  // Simulate API call that returns a value
  const incrementApi = async (value: number): Promise<number> => {
    await new Promise((resolve) => setTimeout(resolve, 300))
    const newCount = value + 1
    setApiCallCount((prev) => prev + 1)
    return newCount
  }

  // Create async throttled increment function
  const throttledIncrement = useAsyncThrottledCallback(
    async (currentValue: number) => {
      const result = await incrementApi(currentValue)
      setCount(result)
      return result
    },
    {
      wait: 1000,
      leading: true, // Execute immediately on first call
      trailing: true, // Execute after throttle period ends
    },
  )

  function handleIncrement() {
    // Update local state immediately for instant feedback
    setCount((prev) => {
      const newCount = prev + 1
      throttledIncrement(newCount)
      return newCount
    })
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 2</h1>
      <table>
        <tbody>
          <tr>
            <td>Current Count:</td>
            <td>{count}</td>
          </tr>
          <tr>
            <td>API Calls Made:</td>
            <td>{apiCallCount}</td>
          </tr>
        </tbody>
      </table>
      <div>
        <button onClick={handleIncrement}>
          Increment (throttled API call)
        </button>
      </div>
      <p style={{ fontSize: '0.9em', color: '#666' }}>
        Click rapidly - API calls are throttled to 1 second, but UI updates
        immediately. First click executes immediately, then at most once per
        second.
      </p>
    </div>
  )
}

function App3() {
  const [scrollPosition, setScrollPosition] = useState(0)
  const [saveCount, setSaveCount] = useState(0)
  const [lastSaved, setLastSaved] = useState<Date | null>(null)
  const [isSaving, setIsSaving] = useState(false)

  // Simulate saving scroll position to server
  const saveScrollPosition = async (
    position: number,
  ): Promise<{ success: boolean; position: number }> => {
    await new Promise((resolve) => setTimeout(resolve, 300))
    return { success: true, position }
  }

  // Create throttled save function
  const throttledSave = useAsyncThrottledCallback(
    async (position: number) => {
      setIsSaving(true)

      try {
        const result = await saveScrollPosition(position)
        setSaveCount((prev) => prev + 1)
        setLastSaved(new Date())
        return result
      } finally {
        setIsSaving(false)
      }
    },
    {
      wait: 1000,
      leading: true,
      trailing: true,
    },
  )

  function handleScroll(e: JSX.TargetedEvent<HTMLDivElement>) {
    const position = e.currentTarget.scrollTop
    setScrollPosition(position)
    throttledSave(position)
  }

  return (
    <div>
      <h1>TanStack Pacer useAsyncThrottledCallback Example 3</h1>
      <div
        style={{
          height: '200px',
          overflow: 'auto',
          border: '1px solid #ccc',
          padding: '10px',
          marginBottom: '20px',
        }}
        onScroll={handleScroll}
      >
        <div style={{ height: '1000px' }}>
          <p>Scroll this area to trigger throttled saves!</p>
          <p>Current scroll position: {Math.round(scrollPosition)}px</p>
          {isSaving && <p style={{ color: 'blue' }}>Saving position...</p>}
          <div style={{ marginTop: '20px' }}>
            <p>Saves triggered: {saveCount}</p>
            {lastSaved && (
              <p>Last saved at: {lastSaved.toLocaleTimeString()}</p>
            )}
          </div>
          <div style={{ marginTop: '40px' }}>
            <p>Keep scrolling...</p>
            <p style={{ marginTop: '100px' }}>More content...</p>
            <p style={{ marginTop: '100px' }}>Even more content...</p>
            <p style={{ marginTop: '100px' }}>Almost there...</p>
            <p style={{ marginTop: '100px' }}>You made it to the end!</p>
          </div>
        </div>
      </div>

      <p style={{ fontSize: '0.9em', color: '#666' }}>
        Scroll position is saved at most once per second, but updates instantly
        on screen
      </p>
    </div>
  )
}

const root = document.getElementById('root')!
render(
  <div>
    <App1 />
    <hr />
    <App2 />
    <hr />
    <App3 />
  </div>,
  root,
)