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
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
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
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
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: []});
CodePudding user response:
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='/'>
...
)