Search code examples
javascriptreactjsreact-routerjsxreact-router-dom

How to create a dynamic route to access a particular page in API


I'm trying to build an ecommerce site product details page in React and React-Router-DOM v6. I have an API where I have some fields like name, title, id, and others.

I already created a product page which display cards, now I want to build the product description page where useParams is given to id, but when I implement the code it shows undefined.

If I put the id value manually in URL in the browser I got the results in console.

Code and screen shots attached.

The first time when URL load on product page, console is also given

enter image description here

The second image when I clicked on card

enter image description here

The third image when I entered the manual value in URL, like "product/17"

enter image description here

Products

import { Col, Container, Row } from "react-bootstrap";
import Navbars from "../Components/Navbars";
import { useState, useEffect } from "react";
import Cookies from "js-cookie";
import "./Products.css";
import { ShoppingCart } from "lucide-react";
import { Link } from "react-router-dom";

function Products() {
  const [products, setProducts] = useState([]);

  const getProduct = async () => {
    try {
      const jwtToken = Cookies.get("jwt_token");

      const apiUrl = "https://apis.ccbp.in/products";

      const options = {
        headers: {
          Authorization: `Bearer ${jwtToken}`,
        },
        method: "GET",
      };

      const response = await fetch(apiUrl, options);
      const data = await response.json();

      setProducts(data.products);

      console.log(data);
      // console.log(data.products[16].id);
    } catch (error) {
      console.error("Error fetching data:", error);
    }
  };

  useEffect(() => {
    getProduct();
  }, []);

  return (
    <>
      <Navbars />

      <Container fluid>
        <Row>
          <Col>
            <Link to={`/products/${products.id}`}>
              <div>
                <h2 className="mains-heading"> All Products</h2>
              </div>
              <div className="p3-5 prods">
                {products.map((item, index) => {
                  const { brand, image_url, price, rating, title } = item;

                  return (
                    <div key={index} className="product-container">
                      <img src={image_url} alt="" className="map-image" />
                      <div className="card-body">
                        <h5 className="card-title text-center pt-2">
                          {title.substr(0, 30)}
                        </h5>
                        <p className="card-text text-center">{`by ${brand}`}</p>

                        <p className="text-center price">{`₹ ${price}`}</p>

                        <p className="ratings text-center">{`⭐  ${rating}`}</p>
                        <div className="btns text-center">
                          <button className="btns">
                            Add to Cart{<ShoppingCart />}{" "}
                          </button>
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>
            </Link>
          </Col>
        </Row>
      </Container>
    </>
  );
}

export default Products;

ProductsDetailedView

import Products from "./Products";
import { Link, useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import Cookies from "js-cookie";

function ProductsDetailPage() {
  const [products, setProducts] = useState([]);
  const params = useParams();
  const { id } = params;
  console.log(id);
  // console.log(params);

  const getProductDetails = async () => {
    try {
      const jwtToken = Cookies.get("jwt_token");

      const apiUrl = "https://apis.ccbp.in/products/" + `${id}`;

      const options = {
        headers: {
          Authorization: `Bearer ${jwtToken}`,
        },
        method: "GET",
      };

      const response = await fetch(apiUrl, options);
      const data = await response.json();
      setProducts(data.products);

      // setProducts(data.products);

      console.log(data);
      // console.log(data.products[16].id);
    } catch (error) {
      console.error("Error fetching data:", error);
    }
  };

  useEffect(() => {
    getProductDetails();
  }, [id]);

  return (
    <>
      this is dynamic route
      <h1> {id}</h1>
    </>
    //   <div>
    //    <Link to= "/products ${id}"> </Link>
    //   </div>
  );
}

export default ProductsDetailPage;

App.js

import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import Login from "./Components/Login";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./Pages/Home";
import Page404 from "./Components/page404";
import Carts from "./Pages/Carts";
import Products from "./Pages/Products";
import Protected from "./Components/Protected";
import ProductsDetailPage from "./Pages/ProductsDetailPage";

function App() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Protected Comp={Home} />} />
          <Route path="/login" element={<Login />} />
          <Route path="/carts" element={<Protected Comp={Carts} />} />
          <Route path="/products" element={<Protected Comp={Products} />} />
          <Route path="/*" element={<Page404 />} />
          <Route path="/products/:id" element={<ProductsDetailPage />} />
        </Routes>
      </BrowserRouter>
    </>
  );
}

export default App;

Solution

  • products is the array of product data, so products.id is undefined.

    Move the Link into the mapped callback so you can correctly link from each specific product by product.id

    Example:

    function Products() {
      const [products, setProducts] = useState([]);
    
      ...
    
      return (
        <>
          <Navbars />
          <Container fluid>
            <Row>
              <Col>
                <div>
                  <h2 className="mains-heading"> All Products</h2>
                </div>
                <div className="p3-5 prods">
                  {products.map((product) => {
                    const { brand, id, image_url, price, rating, title } = product;
    
                    return (
                      {/* link to specific product id */}
                      <Link key={id} to={`/products/${id}`}>
                        <div className="product-container">
                          <img src={image_url} alt="" className="map-image" />
                          <div className="card-body">
                            <h5 className="card-title text-center pt-2">
                              {title.substr(0, 30)}
                            </h5>
                            <p className="card-text text-center">
                              by {brand}
                            </p>
                            <p className="text-center price">
                              ₹ {price}
                            </p>
                            <p className="ratings text-center">
                              ⭐  {rating}
                            </p>
                            <div className="btns text-center">
                              <button className="btns">
                                Add to Cart{<ShoppingCart />}
                              </button>
                            </div>
                          </div>
                        </div>
                      </Link>
                    );
                  })}
                </div>
              </Col>
            </Row>
          </Container>
        </>
      );
    }