why React - component is loading before the img src path is fetched from server?

Viewed 47

Here i am fetching data from a local server via axios via @reduxjs/toolkit. The code below works fine.

function ProductDetails() {
    let dispatch = useDispatch();
    let productsState = useSelector((state) => state.products);
    let prodId = useParams().id;
    useEffect(() => {
        dispatch(fetchProductDetails(prodId));
    }, [dispatch, prodId]);
    let { product, loading, error } = productsState;
    return (
        <Fragment>
            <div className="productDetails">
                <div className="productDetails__left">
                    <p>{product.name}</p>
                </div>
            </div>
        </Fragment>
    );
}

export default ProductDetails;

redux store is working perfectly. Nothing wrong with it.

axiose response:-

{
    "success": true,
    "product": {
        "_id": "62d26e1c19177452c1db9d30",
        "user": "62cd4f2941c20f69eb61d163",
        "name": "HP Pavilion Gaming Laptop",
        "description": "Top perfomance gaming laptop",
        "price": 50000,
        "category": "gaming laptop",
        "ratings": 5,
        "images": [
            {
                "public_id": "placeholder",
                "url": "https://www.reliancedigital.in/medias/HP-15-ec2008AX-Laptops-491997441-i-1-1200Wx1200H-96Wx96H?context=bWFzdGVyfGltYWdlc3w4ODc1OHxpbWFnZS9qcGVnfGltYWdlcy9oMzUvaDhkLzk2MjgyMDUxMjE1NjYuanBnfDY4NGUzYmViMTY3NzM1Mzk3ZmQ4OTA1ZmZlZDAxYmJkNWYwNzUwMTBmMTg3ZDdkNWM1NDJkYWY5MzMyMzExNDc",
                "_id": "62d26e1c19177452c1db9d31"
            }
        ],
        "stock": 19,
        "numOfReviews": 1,
        "reviews": [
            {
                "user": "62d2712709be7c1ae9363ee6",
                "name": "Brenden McCullam",
                "rating": 5,
                "comment": "cool stuff,again!!!",
                "_id": "62d2714b09be7c1ae9363eec"
            }
        ],
        "created_at": "2022-07-16T07:51:56.757Z",
        "__v": 3
    }
}

...But when adding the below line of code in the div. Gives the following error.

<img src={product.images[0].url} alt="product" />

error:

ProductDetails.jsx:20 Uncaught TypeError: Cannot read properties of undefined (reading '0')
    at ProductDetails (ProductDetails.jsx:20:1)
    at renderWithHooks (react-dom.development.js:16305:1)
    at mountIndeterminateComponent (react-dom.development.js:20074:1)
    at beginWork (react-dom.development.js:21587:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at beginWork$1 (react-dom.development.js:27451:1)
    at performUnitOfWork (react-dom.development.js:26557:1)
    at workLoopSync (react-dom.development.js:26466:1)

Replacing the above line of code with the following line of code works fine again :

{product.images ? <img src={product.images[0].url} alt="product" /> : null}

--- or ---

{!loading ? <img src={product.images[0].url} alt="product" /> : null}

So it is obvious that when the component loads img the src product.image don't exist. But product.name and everything else exist for some reason. why is this happening? should i always wrap my whole component in if conditions ? is this the right way to go? pls help?

2 Answers

I'm guessing product is an empty object until the fetch completes, since it doesn't throw at product.name.

Outputting product.name doesn't throw an error because React silently treats the resulting undefined as no content. Trying to index product.images however will throw an error because you're trying to access a property on undefined.

And yes, you should wrap the output in a condition if it tries to access null or undefined otherwise. Or pass the value to a component that handles the empty value case internally.

<div>
  {product.images &&
    <img src={product.images[0].url} alt="product" />
  }
  <OptionalProductImage product={product} /> 
</div>

Add ternary expression to check the URL and if currently not there then use temp image till product image gets.

<img src={product?.images?.[0]?.url || tempImage} alt="product" />

Related