Reputation:
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
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
Reputation: 203208
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.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