How to Fetch Data from an API Using React Query in a React.js Project Like a Pro

In this article, we will explore how to fetch data from an API in a React.js project using React Query. We’ll also look at how to handle loading states and errors gracefully. By the end, you’ll be able to integrate React Query into your projects to streamline data fetching with built-in caching, error handling, and more.
Prerequisites
To follow along, make sure you have:
Basic knowledge of React and React Router.
Node.js and npm installed.
Step 1: Setting Up the Project
First, let’s set up a basic React project with React Query and React Router:
Create a new React app:
npx create-react-app react-query-example cd react-query-exampleInstall the required packages:
npm install @tanstack/react-query react-router-dom
Step 2: Setting Up React Query
React Query requires a QueryClient and a QueryClientProvider to manage API requests. In your main file (e.g., main.jsx), set up React Query like this:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App.jsx';
import Products from './Products.jsx';
import Product from './Product.jsx';
// Create a QueryClient instance
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 10000,
},
},
});
const router = createBrowserRouter([
{ path: '/', element: <App /> },
{ path: '/products', element: <Products /> },
{ path: '/products/:productId', element: <Product /> },
]);
ReactDOM.createRoot(document.getElementById('root')).render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
Step 3: Creating the App Component
We’ll start by creating a simple App component with links to the Products page.
import { Link } from 'react-router-dom';
function App() {
return (
<>
<h1>React Query</h1>
<div>
<Link to="/products">Products</Link>
</div>
</>
);
}
export default App;
Step 4: Fetching Data in the Products Component
Now, let’s fetch a list of products from an API and display them. We’ll use the useQuery hook to fetch data and handle loading and error states.
import { useQuery } from "@tanstack/react-query";
import { Link } from 'react-router-dom';
const fetchProducts = async () => {
const response = await fetch("https://dummyjson.com/products");
if (!response.ok) throw new Error("Failed to fetch products");
const data = await response.json();
return data.products;
};
const Products = () => {
const { isLoading, error, data: products } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts
});
if (isLoading) return <h1>Loading...</h1>;
if (error) return <h1>{error.message}</h1>;
return (
<div>
<h1>Products</h1>
<ul>
{products.map(product => (
<li key={product.id}>
<Link to={`/products/${product.id}`}>{product.title}</Link>
</li>
))}
</ul>
</div>
);
};
export default Products;
In the Products component:
We define an async function
fetchProductsthat fetches data from the API.The
useQueryhook is used to manage the data fetching. It handles caching, loading, and error states automatically.isLoadingistruewhile data is being fetched, so we show a loading message.If
errorexists, we display the error message.Once data is fetched, we map through the
productsand render them as links.
Step 5: Fetching Data in the Product Component
To display individual product details, we’ll set up a Product component with similar error handling.
import { useQuery } from "@tanstack/react-query";
import { useParams } from 'react-router-dom';
const fetchProduct = async (id) => {
const response = await fetch(`https://dummyjson.com/products/${id}`);
if (!response.ok) throw new Error("Failed to fetch product details");
return response.json();
};
const Product = () => {
const { productId } = useParams();
const { isLoading, error, data: product } = useQuery({
queryKey: ['product', productId],
queryFn: () => fetchProduct(productId),
});
if (isLoading) return <h3>Loading...</h3>;
if (error) return <h3>{error.message}</h3>;
return (
<div>
<h1>{product.title}</h1>
<p>Price: ${product.price}</p>
<p>Description: {product.description}</p>
</div>
);
};
export default Product;
In the Product component:
We use the
useParamshook to access theproductIdfrom the URL.The
fetchProductfunction makes a request to the API, passing in theproductId.Similar to the
Productscomponent, we handle loading and error states. If there is an error, we show the error message; otherwise, we display the product details.
Wrapping Up
With React Query, data fetching in React becomes much simpler and more reliable. This example showcases:
Centralized Error and Loading Management: React Query automatically handles loading and error states, which we can customize in our components.
Data Caching: Data fetched with React Query is cached and can be configured to stay fresh or stale based on the
staleTimesetting inQueryClient.
You can now build on top of this foundation, extending your project to include more features or different API endpoints as needed. React Query’s robust features make it an ideal tool for any project that relies on external data fetching.


