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

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:

  1. Create a new React app:

     npx create-react-app react-query-example
     cd react-query-example
    
  2. 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 is true 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 the productId from the URL.

  • The fetchProduct function makes a request to the API, passing in the productId.

  • 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 in QueryClient.

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.