If you look to my JSX I am trying to create sizes from the product object the problem is when I loop throguh the product.size.map() it threw an error that says product is undefined.
How to solve this?
update
I was able to do some workaround but I am sure it is not considered as a best practice.
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match])
if(Object.keys(product).length===0) return null // this is the workaround I have done what do you think?
else{
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{product.size.map(s=><Button key={s} className='p-2 m-1'>{s}</Button>)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}}
export default ProductScreen
import React , {useState , useEffect} from 'react'
import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap'
import {Link} from 'react-router-dom'
import Rating from '../components/Rating'
import axios from 'axios'
const ProductScreen = ({match}) => {
const [product , setProduct] = useState({})
useEffect(()=>{
let componentMounted = true
const fetchProducts = async ()=>{
const {data} = await axios.get(`http://172.30.246.130:5000/api/products/${match.params.id}`)
if(componentMounted){
setProduct(data)
}
}
fetchProducts()
return () => {
componentMounted = false
}
},[match,product ])
return (
<>
<Link className='btn btn-light my-3' to='/'>
Go Back
</Link>
<Row>
<Col md={6}>
<Image src={product.image} alt={product.name} />
</Col>
<Col md={3}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating rating={product.rating} reviews={product.numReviews}/>
</ListGroup.Item>
<ListGroup.Item>
<strong> Price: ${product.price}</strong>
</ListGroup.Item>
<ListGroup.Item>
<strong>Description :</strong> {product.description}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup>
<ListGroup.Item>
<Row>
<Col>
Price :
</Col>
<Col>
<strong>${product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Status :
</Col>
<Col>
<strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
Sizes :
</Col>
<Col>
{console.log(product)}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item className='d-grid gap-2'>
<Button type='button' disabled={product.countInStock === 0}>
Add To Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
</>
)
}
export default ProductScreen
You can't prevent the initial state being rendered.
(I've added an update to the bottom related to the updated question.)
If that component can't be usefully rendered without the product, then product should be a prop it receives, not a state member it fetches. The fetch should be in the parent component.
But if you want the fetch to remain in the component, you need to set a marker value (like null) and branch your rendering based on that marker value, e.g.:
if (!product) {
return /* ...render a "loading" message... */;
}
// ..render `product` here...
Re your edited question: You seem to be having an issue with product.size.map(/*...*/). If we look at your initial state, you have:
const [product, setProduct] = useState({});
Since it doesn't have a size property, product.size is undefined and product.size.map will fail with an error about product.size being undefined.
You haven't shown the code trying to do the product.size.map, but you have various options.
You could use a guard:
{product.size && product.size.map(/*...*/)}
You could include an empty array in the initial state:
const [product, setProduct] = useState({size: []});
Remove product from the dependencies of useEffect as that will trigger the callback and causes an infinite loop.
useEffect(() => {
...
},[match])
To not render anything until product is fetched, use a falsy value like undefined/null as the initial state:
const [product , setProduct] = useState();
and check product before returing the JSX:
if(!product) return null;
return (
<>
<Link className='btn btn-light my-3' to='/'>
...
)
Related
so I've been having this issue with my web page that im unable to solve on my own. Everything works, however when I add Items to my cart instead of aadding the item it replaces the current item with that one. When I go to inspect my CartScreen it displays this error:
react-jsx-dev-runtime.development.js:87 Warning: Each child in a list should have a unique "key" prop.
Check the render method of CartScreen. See https://reactjs.org/link/warning-keys for more information.
i dont get it because I assumed that I hve a unquie key prop at Line:54 I need help if anyone knows the issue here. Here is my CartScreen code:
`
import { useContext } from 'react';
import { Store } from '../Store';
import { Helmet } from 'react-helmet-async';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import MessageBox from '../components/MessageBox';
import ListGroup from 'react-bootstrap/ListGroup';
import Button from 'react-bootstrap/Button';
import Card from 'react-bootstrap/Card';
import { Link, useNavigate } from 'react-router-dom';
import axios from 'axios';
export default function CartScreen() {
const navigate = useNavigate();
const { state, dispatch: ctxDispatch } = useContext(Store);
const {
cart: { cartItems },
} = state;
const updateCartHandler = async (item, quantity) => {
const { data } = await axios.get(`/api/products/${item._id}`);
if (data.countInStock < quantity) {
window.alert('Sorry. Product is out of stock');
return;
}
ctxDispatch({
type: 'CART_ADD_ITEM',
payload: { ...item, quantity },
});
};
const removeItemHandler = (item) => {
ctxDispatch({ type: 'CART_REMOVE_ITEM', payload: item });
};
const checkoutHandler = () => {
navigate('/signin?redirect=/shipping');
};
return (
<div>
<Helmet>
<title>Shopping Cart</title>
</Helmet>
<h1>Shopping Cart</h1>
<Row>
<Col md={8}>
{cartItems.length === 0 ? (
<MessageBox>
Cart is empty. <Link to="/">Go Shopping</Link>
</MessageBox>
) : (
<ListGroup>
{cartItems.map((item) => (
The key is right here:
<ListGroup.Item key={item._id}>
<Row className="align-items-center">
<Col md={4}>
<img
src={item.image}
alt={item.name}
className="img-fluid rounded img-thumbnail"
></img>{' '}
<Link to={`/product/${item.slug}`}>{item.name}</Link>
</Col>
<Col md={3}>
<Button
onClick={() =>
updateCartHandler(item, item.quantity - 1)
}
variant="light"
disabled={item.quantity === 1}
>
<i className="fas fa-minus-circle"></i>
</Button>{' '}
<span>{item.quantity}</span>{' '}
<Button
variant="light"
onClick={() =>
updateCartHandler(item, item.quantity + 1)
}
disabled={item.quantity === item.countInStock}
>
<i className="fas fa-plus-circle"></i>
</Button>
</Col>
<Col md={3}>${item.price}</Col>
<Col md={2}>
<Button
onClick={() => removeItemHandler(item)}
variant="light"
>
<i className="fas fa-trash"></i>
</Button>
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</Col>
<Col md={4}>
<Card>
<Card.Body>
<ListGroup variant="flush">
<ListGroup.Item>
<h3>
Subtotal ({cartItems.reduce((a, c) => a + c.quantity, 0)}{' '}
items) : $
{cartItems.reduce((a, c) => a + c.price * c.quantity, 0)}
</h3>
</ListGroup.Item>
<ListGroup.Item>
<div className="d-grid">
<Button
type="button"
variant="primary"
onClick={checkoutHandler}
disabled={cartItems.length === 0}
>
Proceed to Checkout
</Button>
</div>
</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>
</Col>
</Row>
</div>
);
}
Actually I attempted to follow the instructions from the reactjs.org website on lists-and-keys but it was unsuccessful and I really didnt know how to go about it.
You've got duplicate ids. change it to cartItems.map((item, i) => ...) and use i for the key.
This question already has answers here:
React - TypeError: Cannot read properties of undefined (reading 'params')
(12 answers)
Closed 3 months ago.
I wanna display certain product when it's clicked on based on their id from django rest framework api.. I was doing that using match from the react-router-dom v5, but I don't know how to accomplish the same thing with match not existing in v6. I looked at the react-router-dom v6 documentation for a clue and i couldn't find a solution..
ProductScreen.js:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Row, Col, Image, ListGroup, Button, Card } from "react-bootstrap";
//import products from "../../products";
import Rating from "../Rating";
import axios from "axios";
function ProductScreen({ match }) {
const [product, setProduct] = useState([]);
useEffect(() => {
async function fetchProduct() {
const { data } = await axios.get(`/api/products/${match.params.id}`);
setProduct(data);
}
fetchProduct();
}, []);
return (
<div>
<Link to="/" className="btn btn-dark my-3">
Go Back
</Link>
<Row>
<Col md={6}>
<Image
className="imgCustom"
src={product.image}
alt={product.name}
></Image>
</Col>
<Col md={3}>
<ListGroup variant="flush">
<ListGroup.Item>
<h3>{product.name}</h3>
</ListGroup.Item>
<ListGroup.Item>
<Rating
value={product.rating}
text={`${product.numReviews} reviews`}
color={"f8e825"}
/>
</ListGroup.Item>
<ListGroup.Item>Price : ${product.price}</ListGroup.Item>
<ListGroup.Item>Description: ${product.description}</ListGroup.Item>
</ListGroup>
</Col>
<Col md={3}>
<Card>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col>Price : </Col>
<Col>
<strong>$ {product.price}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Status : </Col>
<Col>
{product.coutInStock > 0 ? "In Stock" : "Out of Stock"}
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Button
className="btn-block"
disabled={product.coutInStock === 0}
type="button"
>
Add to Cart
</Button>
</ListGroup.Item>
</ListGroup>
</Card>
</Col>
</Row>
{product.name}
</div>
);
}
export default ProductScreen;
You can use useParmas for this:
import { useParams } from "react-router-dom";
function ProductScreen() {
const { id } = useParams();
useEffect(() => {
async function fetchProduct() {
const { data } = await axios.get(`/api/products/${id}`);
setProduct(data);
}
fetchProduct();
}, []);
}
can use the useParams to get the id
function ProductScreen({ match }) {
const [product, setProduct] = useState([]);
const { id } = useParams();
useEffect(() => {
async function fetchProduct() {
if(id){
const { data } = await axios.get(`/api/products/${id}`);
setProduct(data);
}
}
fetchProduct();
}, [id]);
I am trying to achieve a behavior on click. What I want is to have the button show “Click to close” when clicked, and then once you click again - revert back to its initial state (showing ‘Easy Riddles’).
Here is a snippet of my code:
import React, { useState } from "react";
import { Accordion, Card, Button, Container, Row, Col } from "react-bootstrap";
const Riddles = (props) => {
const levelStatus = {
easy: "Easy Riddles",
medium: "Intermediate Riddles",
hard: "Hard Riddles",
};
const collapseButton = "Click to close";
const [close, setClose] = useState({
easy: false,
medium: false,
hard: false,
});
// Handle click
const handleClick = (e) => {
setClose({
close.easy: true
});
};
return (
<>
<div className="riddlesection">
<Container>
<Row>
<Col className="riddlegrid" xs={12} sm={12} md={4}>
<Accordion>
<Card id="easy">
<Card.Header>
<Accordion.Toggle
id="easy"
onClick={handleClick}
value="Easy Riddles"
as={Button}
variant="link"
eventKey="0"
>
{close.easy ? levelStatus.easy : collapseButton}
</Accordion.Toggle>
</Card.Header>
<Accordion.Collapse eventKey="0">
<Card.Body>
<Row>
<Col xs={6} sm={6} md={6}>
Countdown
</Col>
<Col className="resetlink" xs={6} sm={6} md={6}>
Switch
</Col>
</Row>
<div>Hello! I'm the body</div>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</Col>
</Row>
</Container>
</div>
</>
);
};
What can I do to achieve differently the behavior that I want?
you need to update the state as below
const handleClick = (e) => {
setClose(prevCloseState => {
...prevCloseState,
easy: true
})
};
I have added an option to pay using paypal but I want to remove the button if the paymentMethod chosen is cash on delivery. Everything works perfectly but I just want to remove the paypal paying option if user selects cash on delivery as paymentMethod. Here is the code:
import React, { useState, useEffect } from 'react'
import { Button, Col, Row, ListGroup, Image, Card } from 'react-bootstrap'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { PayPalButton } from 'react-paypal-button-v2'
import Message from '../components/Message'
import Loader from '../components/Loader'
import { getOrderDetails, payOrder } from '../actions/orderActions'
import { ORDER_PAY_RESET } from '../constants/orderConstants'
const OrderScreen = ({ match }) => {
const orderId = match.params.id
const dispatch = useDispatch()
const [SdkReady, setSdkReady] = useState(false)
const orderDetails = useSelector(state => state.orderDetails)
const { order, error, loading } = orderDetails
const orderPay = useSelector(state => state.orderPay)
const { loading: loadingPay, success: successPay } = orderPay
if(!loading && !error) {
order.itemsPrice = order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0).toFixed(2)
}
const addPayPalScript = () =>{
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://www.paypal.com/sdk/js?client-id=AUBXXXXXXXXXXXXXXXXXXXXXXXEAz9sp6B2QkSR1q7Ia-QXXXXXXXXXXXXXXXXX6s'
script.async = true
script.onload = () => {
setSdkReady(true)
}
document.body.appendChild(script)
}
useEffect(() => {
if (!order || successPay || order._id !== Number(orderId)) {
dispatch({ type:ORDER_PAY_RESET })
dispatch(getOrderDetails(orderId))
} else if(!order.isPaid){
if(!window.paypal) {
addPayPalScript()
} else {
setSdkReady(true)
}
}
}, [dispatch, order, orderId, successPay])
const successPaymentHandler = (paymentResult) => {
dispatch(payOrder(orderId, paymentResult))
}
return loading ? (
<loading/>
) : error ? (
<Message variant='secondary'>{error}</Message>
) : (
<div>
<h1>Order: {order._id}</h1>
<Row>
<Col md={8}>
<ListGroup variant='flush'>
<ListGroup.Item>
<h2>Shipping Address</h2>
<p><strong>Name: </strong>{order.user.name}</p>
<p><strong>Email: </strong><a href={`mailto:${order.user.email}`}>{order.user.email}</a></p>
<p><strong>Phone Number: </strong>{order.shippingAddress.phoneNumber}</p>
<p>
<strong>Shipping: </strong>
{order.shippingAddress.address}, {order.shippingAddress.city},
{' '}
{order.shippingAddress.postalCode},
{' '}
{order.shippingAddress.country}
</p>
{order.isDelivered ? (
<p className="text-center">
<Message variant='primary'>Delivered on {order.deliveredAt}</Message>
</p>
) : (
<p className="text-center">
<Message variant='warning'>Order Confirmed, Your Order will Reach You Soon</Message>
</p>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Payment Method</h2>
<p>
<strong>Method: </strong>
{order.paymentMethod}
</p>
{order.isPaid ? (
<p className="text-center">
<Message variant='primary'>Paid on {order.paidAt}</Message>
</p>
) : (
<p className="text-center">
<Message variant='warning'>Pay For The Product Once The Delivery Has Been Completed {order.paidAt}</Message>
</p>
)}
</ListGroup.Item>
<ListGroup.Item>
<h2>Ordered Items</h2>
{order.orderItems.length === 0 ? <Message variant='secondary'>
Your Order Is Empty
</Message> :(
<ListGroup variant='flush'>
{order.orderItems.map((item, index) => (
<ListGroup.Item key={index}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} fluid rounded />
</Col>
<Col>
<Link to={`/product/${item.product}`}>{item.name}</Link>
</Col>
<Col md={4}>
{item.qty} X ${item.price} = ${(item.qty * item.price).toFixed(2)}
</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
)}
</ListGroup.Item>
</ListGroup>
</Col>
<Col md={4}>
<Card>
<ListGroup variant='flush'>
<ListGroup.Item>
<h2>Order Summary</h2>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Items:</Col>
<Col>${order.itemsPrice}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping:</Col>
<Col>${order.shippingPrice}</Col>
</Row>
</ListGroup.Item>
{/* <ListGroup.Item>
<Row>
<Col>Tax:</Col>
<Col>${order.taxPrice}</Col>
</Row>
</ListGroup.Item> */}
<ListGroup.Item>
<Row>
<Col>Total:</Col>
<Col>${order.totalPrice}</Col>
</Row>
</ListGroup.Item>
{!order.isPaid && (
<ListGroup.Item>
{loadingPay && <Loader/>}
{!SdkReady ? (
<Loader/>
) : (
<PayPalButton
amount={order.totalPrice}
onSuccess={successPaymentHandler}
/>
)}
</ListGroup.Item>
)}
</ListGroup>
</Card>
<ListGroup.Item className='my-3'>
<h3>Delivery Instruction</h3>
<p>
<strong>Instruction: </strong>
{order.shippingAddress.note}
</p>
</ListGroup.Item>
</Col>
</Row>
</div>
)
}
export default OrderScreen
{!order.isPaid && (
Add your (order.paymentMethod == '...') && criterion here.
i use a firebase database and i take data from this base into JSON format. With this data i am using map function and i want to render my data into other components. My code is as shown below. The first component
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
return (
<Row>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</Row>
)
}
The second component that i want to render my data
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{
props.url ? (
<div>
<Card.Img variant="top" src={ props.url.img || 'holder.js/100px160'} />
<Card.Body>
<Card.Title> {props.url.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam according to Nikolas
</Card.Text>
</Card.Body>
</div>
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)
}
</Card>
</Col>
</CardDeck>
</Row>
)
}
With the second component i want to produce the number of Bootstrap-React Cards depending of the number of data i have. For example if i have 6 elements into my JSON file i want in the second component to produce 6 react-bootstrap Cards and print for each some informations like the name.
With the above code i accomplished to pass the props but the props that i console.log is not my data. This is what i get in my console when i
console.log(props)
Can anyone tell how i can pass my data correctly or suggest a better way to do that.
I hope my question is understood. I can give more information i anyone wants
I think this is what you are trying to achieve:
function Products() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("i put a example link here:mydata.json");
setUrl(myurl.data);
}
asyncCall();
}, []);
return (
<Row>
{/*{ {url.map((url => ( */}
{/* the url in the arrow function was shadowing the url array that you were trying to pass to the bags componenet */}
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
{/* )
))} */}
</Row>
);
}
function Bags(props) {
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className="my-3 p-3 rounded">
{props.url.length > 0 ? (
props.url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
);
}
can you please confirm the results?
Try this and tell me if it works
import React, { useEffect, useState } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [url, setUrl] = useState([]);
useEffect(() => {
async function asyncCall() {
const response = await fetch(
"https://mysiteproject-8adcf.firebaseio.com/products.json"
);
const responseJson = await response.json();
console.log(responseJson);
setUrl(responseJson);
}
asyncCall();
}, []);
return (
<div>
{url.map((url => (
<Col key={url.id} sm={12} md={6} lg={4} xl={3}>
<Bags url={url} />
</Col>
)
))}
</div>
);
}
I almost solved the problem with this implementation
function Bags() {
const [url, setUrl] = useState([]);
//const [myfinal,setFinal] = useState([]);
useEffect(() => {
async function asyncCall() {
const myurl = await axios.get("https://mysiteproject-8adcf.firebaseio.com/products.json")
setUrl(myurl.data)
}
asyncCall();
},[]);
if (url) {
//let myvar = url;
//console.log(myvar.img);
//console.log(myvar);
url.map((url) => console.log(url.img));
}
//console.log()
return (
<Row>
<CardDeck>
<Col sm={14} md={8} lg={6}>
<Card className='my-3 p-3 rounded'>
{url.length > 0 ? (
url.map((el) => (
<div>
<Card.Img
variant="top"
src={el.img || "holder.js/100px160"}
/>
<Card.Body>
<Card.Title> {el.name} </Card.Title>
<Card.Text>
This is the greatest albums of rock band Pearl Jam
according to Nikolas
</Card.Text>
</Card.Body>
</div>
))
) : (
<div className="myprogress">
<CircularProgress color="secondary" />
</div>
)}
</Card>
</Col>
</CardDeck>
</Row>
)
}
I don't know if this implementation is optimal