DEV Community

Alex Spinov
Alex Spinov

Posted on

TanStack Query Has a Free API You Should Know About

TanStack Query (formerly React Query) handles server state management with caching, background updates, and stale data handling — all automatically. Its API eliminates most of the boilerplate around data fetching.

Basic Queries

import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"

function UserList() {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: ["users"],
    queryFn: () => fetch("/api/users").then(r => r.json()),
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 10 * 60 * 1000,   // 10 minutes garbage collection
    refetchOnWindowFocus: true,
    retry: 3
  })

  if (isLoading) return <Spinner />
  if (error) return <Error message={error.message} />

  return (
    <ul>
      {data.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

Dependent Queries

function UserPosts({ userId }) {
  const userQuery = useQuery({
    queryKey: ["user", userId],
    queryFn: () => fetchUser(userId)
  })

  const postsQuery = useQuery({
    queryKey: ["posts", userId],
    queryFn: () => fetchUserPosts(userId),
    enabled: !!userQuery.data // Only fetch when user is loaded
  })

  return (
    <div>
      <h1>{userQuery.data?.name}</h1>
      {postsQuery.data?.map(post => <PostCard key={post.id} post={post} />)}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Mutations with Optimistic Updates

function CreatePost() {
  const queryClient = useQueryClient()

  const mutation = useMutation({
    mutationFn: (newPost) => fetch("/api/posts", {
      method: "POST",
      body: JSON.stringify(newPost)
    }).then(r => r.json()),

    // Optimistic update
    onMutate: async (newPost) => {
      await queryClient.cancelQueries({ queryKey: ["posts"] })
      const previous = queryClient.getQueryData(["posts"])
      queryClient.setQueryData(["posts"], (old) => [
        ...old,
        { ...newPost, id: "temp-" + Date.now() }
      ])
      return { previous }
    },

    onError: (err, newPost, context) => {
      queryClient.setQueryData(["posts"], context.previous)
    },

    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["posts"] })
    }
  })

  return (
    <form onSubmit={(e) => {
      e.preventDefault()
      mutation.mutate({ title: e.target.title.value })
    }}>
      <input name="title" />
      <button disabled={mutation.isPending}>Create</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

Infinite Queries

function InfiniteList() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
    queryKey: ["posts"],
    queryFn: ({ pageParam = 1 }) =>
      fetch(`/api/posts?page=${pageParam}`).then(r => r.json()),
    getNextPageParam: (lastPage, pages) =>
      lastPage.hasMore ? pages.length + 1 : undefined,
    initialPageParam: 1
  })

  return (
    <div>
      {data.pages.map(page =>
        page.items.map(item => <Card key={item.id} item={item} />)
      )}
      {hasNextPage && (
        <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
          {isFetchingNextPage ? "Loading..." : "Load More"}
        </button>
      )}
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Prefetching

function PostList() {
  const queryClient = useQueryClient()

  const prefetch = (postId) => {
    queryClient.prefetchQuery({
      queryKey: ["post", postId],
      queryFn: () => fetchPost(postId),
      staleTime: 60000
    })
  }

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id} onMouseEnter={() => prefetch(post.id)}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Automatic caching with configurable stale times
  • Background refetching keeps data fresh
  • Optimistic updates for instant UI feedback
  • Infinite queries for pagination/infinite scroll
  • Prefetching on hover for instant navigation
  • Framework-agnostic — works with React, Vue, Solid, Svelte, Angular

Explore TanStack Query docs for the complete API.


Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.

Top comments (0)