Alright, let’s have an honest conversation. You’ve been using the good old fetch
API with useState
and useEffect
. It works, right? You fetch data, store it in state, and display it. But let’s be real—it gets messy fast. Error handling? Tedious. Caching? A nightmare. What about refetching data when something changes? Forget about it!
So, what if I told you there’s a better way? A way that feels like upgrading from juggling balls to using a conveyor belt. Enter Axios and React-TanStack-Query—tools that make fetching data feel like magic.
Imagine you’re building an app that shows a list of movies. Instead of writing repetitive boilerplate code, these tools let you focus on building features. Ready to upgrade? Let’s dive in!
Why Not Fetch + useState + useEffect?
Before we jump into the new stuff, let’s quickly reflect on why we’re here:
- Repetition: Every time you fetch data, you repeat the same pattern—loading state, error handling, and the fetch call itself.
- Caching: Fetch doesn’t remember the data you’ve already fetched. If you navigate back to the same page, it fetches everything again.
-
Refetching: What if the data changes? With
fetch
, you’d have to manually trigger a reload.
Sound familiar? Let’s fix it.
Step 1: Install Axios and React-TanStack-Query
Start by adding these tools to your project:
npm install axios @tanstack/react-query
We’ll also set up a Query Client, which is like a helper to manage your data.
// /components/providers/QueryProvider.jsx
"use client"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function QueryProvider({ children}) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
// /layout.jsx
import localFont from "next/font/local";
import "./globals.css";
import QueryProvider from "../components/providers/QueryProvider"
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata= {
title: "Tanstack Query with axios",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}) {
return (
<html lang='en'>
<body
className={``}
>
<QueryProvider>{children}</QueryProvider>
</body>
</html>
);
}
That’s it for setup. Now let’s fetch some data!
Fetching Data the React-TanStack-Query Way
Let’s rewrite a simple fetch
example using React-TanStack-Query. Imagine we’re building a movie app and need to fetch a list of movies:
Using Fetch + useState + useEffect (The Old Way)
import { useEffect, useState } from "react";
export default function Movies() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.example.com/movies")
.then(res => res.json())
.then(data => {
setMovies(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <p>Loading movies...</p>;
if (error) return <p>Error loading movies: {error.message}</p>;
return (
<ul>
{movies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
);
}
Now, let’s simplify this with React-TanStack-Query.
Using React-TanStack-Query (The Better Way)
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
const fetchMovies = async () => {
const response = await axios.get("https://api.example.com/movies");
return response.data;
};
export default function Movies() {
const { data: movies, error, isLoading } = useQuery(["movies"], fetchMovies);
if (isLoading) return <p>Loading movies...</p>;
if (error) return <p>Error loading movies: {error.message}</p>;
return (
<ul>
{movies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
);
}
What Just Happened?
-
useQuery
: This magical hook takes care of fetching, caching, and error handling. No moreuseState
oruseEffect
juggling! - Automatic Refetching: If something changes, your data stays up-to-date without you lifting a finger.
-
Axios: We use Axios to fetch data, which is easier to work with than the
fetch
API.
Customizing Axios for Your App
In a real app, you might need to add headers, a base URL, or an authentication token to your requests. Here’s how you can create a reusable Axios instance:
// utils/axios.js
import axios from "axios";
const axiosInstance = axios.create({
baseURL: "https://api.example.com",
headers: {
Authorization: `Bearer ${process.env.API_TOKEN}`,
},
});
export default axiosInstance;
Now use this instance in your query:
import { useQuery } from "@tanstack/react-query";
import axiosInstance from "../utils/axios";
const fetchMovies = async () => {
const response = await axiosInstance.get("/movies");
return response.data;
};
export default function Movies() {
const { data: movies, error, isLoading } = useQuery(["movies"], fetchMovies);
if (isLoading) return <p>Loading movies...</p>;
if (error) return <p>Error loading movies: {error.message}</p>;
return (
<ul>
{movies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
);
}
Why Use React-TanStack-Query?
Here’s why it’s worth ditching fetch
for TanStack Query:
- Caching: Your data is cached, so if you revisit the page, it doesn’t refetch unless necessary.
-
Error Handling: No need for messy
try/catch
blocks—it’s built-in. - Stale-While-Revalidate: TanStack Query shows cached data immediately while it fetches fresh data in the background.
- Flexibility: You can easily customize fetching, polling, retries, and more.
Bonus: Pagination Example
What if you’re fetching a paginated API? TanStack Query has you covered:
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";
const fetchMovies = async (page) => {
const response = await axios.get(`https://api.example.com/movies?page=${page}`);
return response.data;
};
export default function PaginatedMovies() {
const [page, setPage] = useState(1);
const { data, error, isLoading } = useQuery(["movies", page], () => fetchMovies(page));
if (isLoading) return <p>Loading movies...</p>;
if (error) return <p>Error loading movies: {error.message}</p>;
return (
<div>
<ul>
{data.movies.map(movie => (
<li key={movie.id}>{movie.title}</li>
))}
</ul>
<button onClick={() => setPage((prev) => Math.max(prev - 1, 1))}>Previous</button>
<button onClick={() => setPage((prev) => prev + 1)}>Next</button>
</div>
);
}
Wrapping It Up
Switching to React-TanStack-Query feels like upgrading from a bicycle to a Tesla. It takes care of caching, error handling, and refetching for you, so you can focus on building cool features.
If you’re tired of boilerplate code, give it a shot. You’ll thank yourself later!
Top comments (0)