# 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**:
    
    ```bash
    npx create-react-app react-query-example
    cd react-query-example
    ```
    
2. **Install the required packages**:
    
    ```bash
    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:

```jsx
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.

```jsx
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.

```jsx
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.

```jsx
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.
