DEV Community

Cover image for Why TanStack Query is overrated (and what to use instead)
Scott Hu
Scott Hu

Posted on

Why TanStack Query is overrated (and what to use instead)

⚠️ 【待 review】

Why TanStack Query is overrated (and what to use instead)

I've been a TanStack Query user for two years. I wrote about it, recommended it to my team, and genuinely thought it was the final form of frontend data fetching.

Then I built a real product — a full-blown admin dashboard with forms, paginated lists, file uploads, polling, and cross-component communication.

That's when I realized: TanStack Query gives you a glorified fetcher and leaves all the complexity to you.

It's not that TanStack Query is bad. It's excellent at what it does: query caching, request deduplication, and background refetching. But in real-world application development, "fetching data" is only a fraction of what you actually need.

Here are 5 scenarios where I found alova drastically out-performs TanStack Query.


1. Form Submission

TanStack Query

const { mutateAsync } = useMutation({
  mutationFn: (data) => axios.post('/api/form', data),
})

const { register, handleSubmit, reset, watch } = useForm({
  defaultValues: loadFromDraft()
})

useEffect(() => {
  saveDraft(watch())
}, [watch()])

const onSubmit = async (data) => {
  await mutateAsync(data)
  reset()
  clearDraft()
}
Enter fullscreen mode Exit fullscreen mode

You need React Hook Form (or Formik), manual draft persistence, manual reset after submit — easily 30+ lines for what should be a one-liner.

alova

const {
  loading, submit, form, reset
} = useForm(
  (formData) => alova.Post('/api/form', formData),
  {
    store: true,                // auto draft persistence
    resetAfterSubmiting: true,  // auto reset after submit
  }
)
Enter fullscreen mode Exit fullscreen mode

3 lines. Draft persistence survives page refresh. Multi-step form state is shared automatically. No useEffect, no localStorage calls.

The philosophy difference: TanStack Query gives you a primitive and says "figure it out." alova gives you a complete form abstraction and says "just use it."


2. Pagination / Infinite Scroll

TanStack Query

const [page, setPage] = useState(1)

const { data, isPreviousData } = useQuery({
  queryKey: ['list', page],
  queryFn: () => fetch(`/api/list?page=${page}`),
  keepPreviousData: true,
})

// Manual prefetch
const queryClient = useQueryClient()

const prefetchNext = () => {
  queryClient.prefetchQuery(['list', page + 1], ...)
}

useEffect(() => {
  if (data?.length < pageSize) return
  prefetchNext()
}, [page, data])
Enter fullscreen mode Exit fullscreen mode

This is just page management. Once you add edit-then-refresh or delete-then-remove, the boilerplate doubles.

alova

const {
  data, loading, page, pageSize,
  handlePrevPage, handleNextPage, reload
} = usePagination(
  (page, pageSize) => alova.Get('/api/list', {
    params: { page, pageSize }
  }),
  {
    preloadNextPage: true,   // auto prefetch next page
    total: res => res.total,
  }
)
Enter fullscreen mode Exit fullscreen mode

usePagination manages the entire pagination lifecycle — page state, prefetching, list mutation — in one hook. Deleting an item? It's automatically removed from the list. Editing one? Automatically updated.


3. Smart Polling + Focus Refetch

TanStack Query

const { data } = useQuery({
  queryKey: ['notification'],
  queryFn: () => fetch('/api/notification'),
  refetchInterval: 5000,
  refetchOnWindowFocus: true,
})
Enter fullscreen mode Exit fullscreen mode

Looks fine until you need dynamic control:

const [enabled, setEnabled] = useState(false)

const { data } = useQuery({
  queryKey: ['notification'],
  queryFn: () => fetch('/api/notification'),
  refetchInterval: enabled ? 5000 : false,
  refetchOnWindowFocus: enabled,
})
Enter fullscreen mode Exit fullscreen mode

alova

const { loading, data, stop, resume } = useAutoRequest(
  () => alova.Get('/api/notification'),
  {
    enablePoll: true,
    pollInterval: 5000,
    enableThrottle: true,      // stop when tab is hidden
    enableVisibility: true,    // resume when tab regains focus
  }
)

// Full control
stop()   // pause
resume() // restart
Enter fullscreen mode Exit fullscreen mode

Polling, focus refetch, throttling, and visibility control — all in one hook. Call stop() and resume() whenever you need.


4. Cross-Component Communication

TanStack Query

// Component A
const mutation = useMutation({
  mutationFn: (data) => axios.post('/api/item', data),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['list'] })
  },
})
Enter fullscreen mode Exit fullscreen mode

As your app grows, invalidateQueries calls scatter across your codebase. Tracking queryKey dependencies becomes a manual mental exercise.

alova

// Declarative cache invalidation
const updateTodo = (id, data) =>
  alova.Put(`/api/todo/${id}`, data, {
    hitSource: 'todoList',  // auto-invalidate todoList cache
  })
Enter fullscreen mode Exit fullscreen mode

Anyone reading the code immediately sees: "updating a todo → refreshes the list." No need to hunt down mutation callbacks.

For more complex scenarios, actionDelegationMiddleware lets you call another component's request method from anywhere — no prop drilling, no global store:

// In Component A:
const { send } = useRequest(updateTodo, {
  middleware: actionDelegationMiddleware('updateTodo')
})

// In Component B (anywhere in the tree):
accessAction('updateTodo', ({ send }) => { send() })
Enter fullscreen mode Exit fullscreen mode

5. File Upload

TanStack Query — No native support

You'd write your own upload logic with FormData, progress events, concurrency control, and pause/resume. Every. Single. Time.

alova

const {
  upload, loading, progress
} = useUploader(
  ({ file }) => alova.Post('/api/upload', { file }),
  { limit: 3 }  // max 3 concurrent uploads
)

// Pause / resume
controller.pause()
controller.resume()
Enter fullscreen mode Exit fullscreen mode

6 lines. Progress tracking, concurrency control, pause/resume — all built in.


The Bottom Line

Scenario TanStack Query alova
Basic GET + cache ✅ Works well ✅ Works well
Form + draft + reset ❌ DIY ~200 lines ✅ useForm: 3 lines
Pagination + prefetch ❌ DIY ✅ usePagination
Smart polling + throttle ❌ DIY ✅ useAutoRequest
Cross-component communication ⚠️ invalidateQueries ✅ hitSource / actionDelegation
File upload + concurrency ❌ Not supported ✅ useUploader
Bundle size ~13KB gzipped ~4KB gzipped

TanStack Query is an excellent library. For simple data fetching and caching, it's a solid choice.

But if your application deals with forms, paginated lists, file uploads, or any of the "messy real-world" request scenarios, alova's strategy hooks will save you hundreds of lines of code and countless hours of debugging.

Give it a try — you might be surprised how simple data fetching can be.


📦 alova v3 is now live | GitHub | Docs

Top comments (0)