Skip to main content

Overview

The useProduct hook fetches detailed information for a single product by its ID. It’s commonly used on product detail pages and includes automatic enabling/disabling based on ID availability. Source: src/hooks/useProduct.js:4

Function Signature

export const useProduct = (id) => {
  return useQuery({
    queryKey: ["product", id],
    queryFn: () => productService.getById(id),
    enabled: !!id,
  });
};

Parameters

id
number
required
The numeric ID of the product to fetch

Return Value

Returns a React Query result object:
data
object | undefined
Product object with detailed information
id
number
Product ID
title
string
Product title/name
description
string
Detailed product description
price
number
Product price
thumbnail
string
URL of the product thumbnail image
images
string[]
Array of product image URLs
category
string
Product category slug
rating
number | object
Product rating (format varies by API)
discountPercentage
number
Discount percentage if applicable
isLoading
boolean
true when the product data is being fetched
isError
boolean
true if an error occurred during fetching
error
Error | null
Error object if the request failed

Enabled Query Behavior

The query is automatically disabled when id is falsy (via enabled: !!id). This prevents unnecessary API calls when the ID hasn’t been set yet.
// Won't fetch until id is available
const { data: product } = useProduct(undefined); // No API call
const { data: product } = useProduct(null);      // No API call
const { data: product } = useProduct(0);         // No API call
const { data: product } = useProduct(42);        // Fetches product #42

Usage Examples

import { useParams, useNavigate } from "react-router-dom";
import { useProduct } from "../../hooks/useProduct";
import { useCartStore } from "../../store/useCartStore";

export const ProductDetail = () => {
  const { id } = useParams();
  const navigate = useNavigate();
  const numericId = Number(id);

  const { data: product, isLoading, isError } = useProduct(numericId);
  const addToCart = useCartStore((state) => state.addToCart);

  if (isLoading) {
    return (
      <div className="container py-5">
        <div className="spinner-border" role="status" />
        <h4 className="mt-3">Loading product...</h4>
      </div>
    );
  }

  if (isError || !product) {
    return (
      <div className="container py-5">
        <h4>Product not found</h4>
        <button onClick={() => navigate("/")}>← Back to Store</button>
      </div>
    );
  }

  const handleAddToCart = () => {
    addToCart({
      id: product.id,
      title: product.title,
      price: product.price,
      thumbnail: product.thumbnail,
    });
  };

  return (
    <div className="product-detail-container">
      <h1>{product.title}</h1>
      <p className="price">${product.price.toFixed(2)}</p>
      <p>{product.description}</p>
      <button onClick={handleAddToCart}>Add to Cart</button>
    </div>
  );
};

URL Parameter Pattern

Common pattern when using with React Router:
import { useParams } from "react-router-dom";
import { useProduct } from "../hooks/useProduct";

const ProductPage = () => {
  const { id } = useParams(); // String from URL
  const numericId = Number(id); // Convert to number
  
  const { data: product } = useProduct(numericId);
  // ...
};

API Service

The hook uses productService.getById(id) which makes a GET request to /products/{id}. Source: src/services/productService.js:8

Caching Strategy

Each product is cached individually with the query key ["product", id]. This means:
  • Different products have separate cache entries
  • Products remain cached even when navigating between pages
  • Switching back to a previously viewed product shows cached data instantly

Error Handling

From the real implementation (src/pages/productDetail/ProductDetail.jsx:32):
if (isError || !product) {
  return (
    <div className="container py-5">
      <div className="product-detail-error">
        <h4>Product not found</h4>
        <button onClick={() => navigate("/")}>← Back to Store</button>
      </div>
    </div>
  );
}

Build docs developers (and LLMs) love