user18309088
user18309088

Reputation:

ReactJs Product Details Page

I have an API with a working Bearer token.

I am listing all three products from the API on a product page. I would like to be able to click any of the items and display the information, on a product item detail page I created.

I have successfully created my routes and I am able to click around and open the relevant pages. The issue now is pulling details of an individual item on a item detail page using params, props, and match in ReactJS.

This is App.js file

import "./index.scss";
import React from "react";
import {
  BrowserRouter as Router,
  Route,
  Routes,
} from "react-router-dom";

import Discover from "./pages/discover";
import ItemDetail from "./pages/itemDetail";

function App() {
  return (
    <Router>
      <Routes>
        <Route path="discover" exact element={<Discover />} />
        <Route path="/itemDetail/:productCode" exact element={<ItemDetail />} />
      </Routes>
    </Router>
  );
}

export default App;

This is the products page, I have named it discover I am rendering all the products from the API on custom card components.

import React, { useState, useEffect } from "react";
import { Row, Col } from "react-bootstrap";
import StyledCard from "../components/Card";

const Discover = (props) => {
  const token = "3f224802-426e-3597-a2eb-7d35e6bff31d";
  const [result, setResult] = useState([]);

  useEffect(() => {
    fetch(
      "https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/product/",
      {
        method: "GET",
        headers: { Authorization: `Bearer ${token}` },
      }
    )
      .then((res) => res.json())
      .then((json) => setResult(json));
  }, []);

  const cardStyle = {
    listStyle: "none",
    margin: 5,
    paddingLeft: 0,
    minWidth: 240,
  };
  return (
    <>
      <div className="latestdeals container my-5">
        <h1>All Products</h1>
        <Row className="hotcards">
          <Col className="colcard">
            {(result?.result || []).map((item) => (
              <div key={item.productCode} style={cardStyle}>
                <a href={`/itemDetail/${item.productCode}`}>
                  {" "}
                  <StyledCard
                    key={item.productCode}
                    name={item.vendor}
                    title={item.description}
                    price={item.value}
                  />
                </a>
              </div>
            ))}
          </Col>
        </Row>
      </div>
    </>
  );
};

export default Discover;

Below is the item details page where I would like to display all the data inside that 1 specific item. I would also like to add the item to the cart (cart page) in the future.

import React, {useEffect, useState} from 'react';

function ItemDetail({ match }) {

    const [item, setItem] = useState({});
    const token = "3f224802-426e-3597-a2eb-7d35e6bff31d";

    useEffect(() => {
      fetch(
        "https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/product/",
        {
          method: "GET",
          headers: { Authorization: `Bearer ${token}` },
        }
      )
        .then((res) => res.json())
        .then((json) => setItem(json));
    }, []);

    console.log(item);

  return (
    <div>Item Detail</div>
  )
}

export default ItemDetail;

Now finally below is the results I get from the API

{
    "result": [
        {
            "description": "PlayStation Store R50 Voucher ",
            "productCode": "317",
            "value": 50,
            "vendor": "Sony PlayStation"
        },
        {
            "description": "R1 - R2500 1Voucher Token",
            "productCode": "311",
            "value": 0,
            "vendor": "1Voucher"
        },
        {
            "description": "Refund 1Voucher Token",
            "productCode": "392",
            "value": 0,
            "vendor": "1Voucher"
        }
    ],
    "status": "Success"
}

Upvotes: 3

Views: 4630

Answers (2)

Julia
Julia

Reputation: 29

import React, { useEffect, useState } from 'react'
import { Helmet } from 'react-helmet'
import Header from "../../components/Header"
import { Link, useParams } from 'react-router-dom'
import axios from 'axios'
import "./Detail.scss"
const Detail = () => {
  const {id}= useParams()
  const [data,setData]=useState([])
  useEffect(()=>{
    axios.get(`http://localhost:8080/robots/${id}`).then((res)=>{
      setData(res.data)
    })
  })
  return (
    <>
        <Header/>
    <div className="detail">
        <Helmet>
            <title>Detail Pages</title>
        </Helmet>


        <img src={data.image} alt="" />
        <h1>{data.title}</h1>
        <p>{data.desc}</p>
        <p>${data.price}</p>
        <Link to="/"> <button>Back to Home</button></Link>
    </div>
    </>
  )
}

export default Detail

Upvotes: 1

Drew Reese
Drew Reese

Reputation: 203208

Issues

  • The Discover component is rendering a raw anchor (<a />) tag instead of the Link component provided from react-router-dom. This will reload the page when clicked, losing any saved React state.
  • ItemDetail doesn't need to refetch the same data that Discover did. A simple solution could be to send the specific item in route state to the details page.

Solution

Discover

Import the Link component and replace the anchor tag with it. Pass the current mapped item in route state. Remember to save the response result property into state instead of the entire result object.

import { Link } from 'react-router-dom';

const Discover = (props) => {
  const token = "XXXXX";
  const [result, setResult] = useState([]);

  useEffect(() => {
    fetch(
      "https://api.flash-internal.flash-group.com/ecommerceManagement/1.0.0/api/product/",
      {
        method: "GET",
        headers: { Authorization: `Bearer ${token}` },
      }
    )
      .then((res) => res.json())
      .then((data) => setResult(data.result)); // <-- save data.result
  }, []);

  ...

  return (
    <div className="latestdeals container my-5">
      <h1>All Products</h1>
      <Row className="hotcards">
        <Col className="colcard">
          {result.map((item) => (
            <div key={item.productCode} style={cardStyle}>
              <Link // <-- use Link component
                to={`/itemDetail/${item.productCode}`}
                state={{ item }} // <-- pass item object in state
              >
                <StyledCard
                  key={item.productCode}
                  name={item.vendor}
                  title={item.description}
                  price={item.value}
                />
              </Link>
            </div>
          ))}
        </Col>
      </Row>
    </div>
  );
};

ItemDetail

In react-router-dom@6 the Route component API changed a bit from v5, there are no longer any route props, i.e. no history, location, and match props.

ItemDetail is a function component so it can use the React hooks, specifically the useLocation hook to access the passed route state. Since the data is passed in route state there's no need for the additional GET request.

Example:

import React, {useEffect, useState} from 'react';
import { useLocation } from 'react-router-dom';

function ItemDetail() {
  const { state } = useLocation(); // <-- access route state

  const { item } = state || {}; // <-- unpack the item from state
    
  console.log(item);

  

  return item ? (
    <div>
      <p>{item.description]</p>
      <p>{item.productCode]</p>
      <p>{item.value]</p>
      <p>{item.vendor]</p>
    </div>
  ) : "No item matched/found.";
}

export default ItemDetail;

Upvotes: 1

Related Questions