DEV Community

Cover image for RTK Query vs Custom Axios Hooks: What Actually Happens When Your Project Grows
Hossain MD Athar
Hossain MD Athar

Posted on

RTK Query vs Custom Axios Hooks: What Actually Happens When Your Project Grows

I started a React project with the usual “simple” setup: Axios, a custom data-fetching hook, and a clean little useEffect here and there. Early on, it felt totally manageable. One hook per endpoint, a bit of state for loading, and everything stayed readable.

But once the app grew, the weaknesses of that setup showed up fast. Different parts of the app needed the same data, hooks started duplicating logic, and suddenly every new feature required more boilerplate. I kept feeling like I was rebuilding the same patterns across the codebase.

That’s when I committed to RTK Query—and the difference was immediate.

The RTK Query Version (clean, obvious, predictable)

Fetching data with RTK Query feels almost peaceful. The intent of the component is clear at a glance:

const dispatch = useDispatch();

// RTK Query: fetch ticket data with automatic caching, loading state, and refetch logic
const { data: ticket, isLoading } = useGetTicketsByCompanyIdQuery(
  company?.id ?? 0,
  { skip: !company?.id }
);
Enter fullscreen mode Exit fullscreen mode

That’s the entire fetching setup. No manual state, no useEffect, no memoization, no “did this refetch correctly?” debugging.

Why it reads better

The component tells you what data it needs, not how to get it.

Why it scales better

Data is cached and shared automatically across components. Duplicate requests disappear. Adding new endpoints keeps code centralized (API slice), not scattered across hooks.

Why it’s more efficient

Built-in caching prevents redundant network calls. Invalidations and refetching are handled by a unified system, not random effects.

The Axios Hook Version (works… until it doesn’t)

Here’s the hook-based version:

const dispatch = useDispatch();

// Custom hook using Axios
const { fetchData } = useGetData<TicketDetails[]>();

useEffect(() => {
  const fetchTickets = async () => {
    const apiRoute = `/api/tickets/company/${company?.id}`;
    const response = await fetchData(apiRoute);
    response && setTickets(response); // assuming local useState
  };

  fetchTickets();
}, [company, fetchData]);
Enter fullscreen mode Exit fullscreen mode

Nothing wrong with it… but look at all the moving parts:

  • Manual loading state
  • Manual caching (if you want it)
  • Manual dependency management
  • Manual refetch rules
  • Manual error handling
  • Local state everywhere

This grows messy quickly because each component is responsible for its own data lifecycle.

Readability issues

Your UI logic gets mixed with loading logic, effect logic, error handling, and state updates. Two months later, you forget why something re-fetches—or why it doesn’t.

Scalability issues

As soon as multiple components need the same API data, you either: duplicate calls, or build your own caching layer (which… is basically reinventing RTK Query).

Efficiency issues

Every mount triggers a fetch unless you build custom memoization/caching. Not wrong—just extra work.

RTK Query gives you:

  • shared data
  • request deduping
  • caching
  • invalidation
  • polling
  • refetch-on-focus
  • consistent patterns

Without you maintaining any of it.

Final Thoughts

If your project is tiny, Axios hooks are fine. But once your state logic starts spreading out—multiple components relying on shared data, multiple endpoints, or complex flows—RTK Query shines.

You get:

  • cleaner components
  • clearer intent
  • centralized logic
  • fewer bugs
  • fewer network calls

If you’re feeling your data-fetching logic getting harder to wrangle, that’s usually the moment RTK Query becomes worth it. It made my project much easier to maintain as it grew.

Top comments (0)