Home > Software engineering >  preventing useState to render the initial value
preventing useState to render the initial value

Time:11-11

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='/'>
 ...
)
  • Related