Framework
Version
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
  })

  // 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>,
)
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
  })

  // 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>,
)
Our Partners
Unkey
Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Bytes

No spam. Unsubscribe at any time.

Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Bytes

No spam. Unsubscribe at any time.