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-example
Install 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
fetchProducts
that fetches data from the API.The
useQuery
hook is used to manage the data fetching. It handles caching, loading, and error states automatically.isLoading
istrue
while data is being fetched, so we show a loading message.If
error
exists, we display the error message.Once data is fetched, we map through the
products
and 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
useParams
hook to access theproductId
from the URL.The
fetchProduct
function makes a request to the API, passing in theproductId
.Similar to the
Products
component, 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
staleTime
setting 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.