Docs
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Prisma
Strapi
Unkey
UI.dev
CodeRabbit
Cloudflare
AG Grid
Netlify
Neon
WorkOS
Clerk
Convex
Electric
PowerSync
Sentry
Prisma
Strapi
Unkey
UI.dev
Debouncer API Reference
Throttler API Reference
Rate Limiter API Reference
Queue API Reference
Batcher API Reference

React Example: React Query Throttled Prefetch

tsx
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom/client'
import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { useThrottledValue } from '@tanstack/react-pacer'

// Fetch all posts
const fetchPosts = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts')
  return response.json()
}

// Fetch a single post
const fetchPost = async (id: number) => {
  await new Promise((resolve) => setTimeout(resolve, 1000)) // Simulate a slow response
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${id}`,
  )
  return response.json()
}

function PostList({
  setSelectedPostId,
}: {
  setSelectedPostId: (id: number) => void
}) {
  // Simple query that fetches all posts
  const { data: posts, isLoading } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
  })

  // keep track of the hovered post id that could be prefetched
  const [currentHoveredPostId, setCurrentHoveredPostId] = React.useState<
    number | null
  >(null)

  // throttle the hovered post id to avoid excessive prefetches
  const [throttledHoveredPostId] = useThrottledValue(currentHoveredPostId, {
    wait: 100, // adjust this value to see the difference
    // Alternative to throttler.Subscribe: pass a selector as 3rd arg to cause re-renders and subscribe to state
    // (state) => state,
  })

  // perform the prefetch when the throttled hovered post id changes
  useEffect(() => {
    if (throttledHoveredPostId) {
      queryClient.ensureQueryData({
        queryKey: ['post', throttledHoveredPostId],
        queryFn: () => fetchPost(throttledHoveredPostId),
      })
    }
  }, [throttledHoveredPostId])

  const handleMouseEnter = (postId: number) => {
    setCurrentHoveredPostId(postId) // update the hovered post id
  }

  if (isLoading) return <div>Loading posts...</div>

  return (
    <div>
      <h2>Posts</h2>
      <ul style={{ margin: 0, padding: 0 }}>
        {posts?.map((post: { id: number; title: string }) => (
          <li key={post.id} style={{ margin: '2px 0' }}>
            <a
              href={`#post-${post.id}`}
              onMouseEnter={() => handleMouseEnter(post.id)}
              onClick={() => setSelectedPostId(post.id)}
              style={{ display: 'block', padding: '4px', cursor: 'pointer' }}
            >
              {post.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  )
}

function PostDetail({ postId }: { postId: number }) {
  const { data: post, isLoading } = useQuery({
    queryKey: ['post', postId],
    queryFn: () => fetchPost(postId),
  })

  if (isLoading) return <div>Loading post...</div>

  return (
    <div>
      <h3>{post?.title}</h3>
      <p>{post?.body}</p>
    </div>
  )
}

function App() {
  const [selectedPostId, setSelectedPostId] = React.useState<number | null>(
    null,
  )

  return (
    <div
      className="App"
      style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}
    >
      <h1>TanStack Pacer/Query Throttled Prefetch Example</h1>
      <p>Hover over a post title to prefetch its content</p>
      <p>
        This example shows how to prefetch a query when the user hovers over a
        post.
      </p>
      <p>
        The throttled query key is created after a throttle to avoid excessive
        prefetches.
      </p>
      <div
        style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}
      >
        <PostList setSelectedPostId={setSelectedPostId} />
        {selectedPostId && <PostDetail postId={selectedPostId} />}
      </div>
    </div>
  )
}

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 10_000,
    },
  },
})

const root = ReactDOM.createRoot(document.getElementById('root')!)
root.render(
  <QueryClientProvider client={queryClient}>
    <App />
    <ReactQueryDevtools initialIsOpen={false} />
  </QueryClientProvider>,
)