Reputation: 492
I am trying to retrieve a array inside a state, then go for a map, but returns undefined. It seems the state is not ready but have no idea why.
I tried a lot of alternatives but even the length I cannot get, it also returns undefined. Only the data in function getProductDetails I can get length, no other place. So this is why I think is something related to the state readiness, but no idea how to solve.
Any help would be appreciated.
import React, { Component } from 'react';
import propTypes from 'prop-types';
import { Link } from 'react-router-dom';
import cartIcon from '../images/cartIcon.jpg';
import backIcon from '../images/backIcon.jpg';
import '../styles/ProductDetails.css';
export default class ProductDetails extends Component {
constructor() {
super();
this.state = {
product: {},
};
}
componentDidMount() {
this.getDetails();
}
async getProductDetails(item) {
const url = `https://api.mercadolibre.com/items/${item}`;
const response = await fetch(url);
const data = await response.json();
return data;
}
async getDetails() {
const { match: { params: { id } } } = this.props;
const product = await this.getProductDetails(id);
this.setState({
product,
});
}
render() {
const { product } = this.state;
return (
<section className="details">
<div className="details__header">
<div className="details__back">
<Link
to="/"
className="back__btn"
data-testid="shopping-back-button"
>
<img
id="back-button"
name="back-button"
alt="Voltar"
src={ backIcon }
className="back__img"
/>
</Link>
</div>
<div className="details__cart">
<Link
to="/carrinho-de-compras"
className="cart__btn"
data-testid="shopping-cart-button"
>
<img
id="cart-button"
name="cart-button"
alt="Carrinho de Compras"
src={ cartIcon }
className="cart__img"
/>
</Link>
</div>
</div>
<div className="details__product">
<div className="details__left">
<p data-testid="product-detail-name">{ product.title }</p>
<img src={ product.thumbnail } alt={ product.title } />
<p>{ product.id }</p>
<p>{ product.price }</p>
</div>
<div className="details__right">
<ul>
{(product.attributes)
.map(({ name, value_name: valueName }, index) => (
<li key={ index }>
{name}
:
{valueName}
</li>))}
</ul>
</div>
</div>
</section>
);
}
}
ProductDetails.propTypes = {
match: propTypes.shape({
params: propTypes.shape({
id: propTypes.string,
}),
}).isRequired,
};
Upvotes: 1
Views: 1128
Reputation: 202618
The initial this.state.product
value is an empty object {}
, so this.state.product.attributes
is undefined on the initial render and not mappable.
this.state = {
product: {},
};
product.title
, product.id
, and product.price
are OFC also undefined on the initial render but since you are not accessing more deeply these are rendered to the DOM as just undefined values and no error is thrown.
You have a few options to guard against the possibly null/undefined access:
Use null-check/guard-clause on this.state.product.attributes
<ul>
{product.attributes
&& product.attributes.map(({ name, value_name: valueName }, index) => (
<li key={index}>
{name}: {valueName}
</li>
))}
</ul>
Use optional chaining operator on this.state.product.attributes
<ul>
{product.attributes?.map(({ name, value_name: valueName }, index) => (
<li key={index}>
{name}: {valueName}
</li>
))}
</ul>
Upvotes: 1
Reputation: 3723
That's because your this.state.product
will only be available after you call this.getDetails()
to fetch and set data in componentDidMount
.
So the first time your component renders (aka mount), this.state.product
will not be available.
To solve that issue, one common approach would be to check this.state.product
in render()
method like this.
render() {
const { product } = this.state;
// if data is NOT available
if (!product) {
// either return null, or render a loading indicator, or whatever you want, while waiting for `this.state.product` to be available
return null
}
// if data is available
return (
<section className="details">
// render your component here
</section>
);
}
Upvotes: 0