Ваш AppProvider
компонент правильный, единственная проблема, с которой вы столкнулись, это то, что вы выбираете результаты из API и делаете это в componentDidMount
, что должно быть правильным.
Однако, когда вы обновляете sh страницу, установленную в AppProvider
, сбрасывается до начального значения, и, поскольку вы уже используете компонент ProductDetail
, вы пытаетесь получить доступ к значениям продукта даже до того, как он станет доступен из Запрос API и, следовательно,
const filteredProduct = products.filter(p => p.id == props.match.params.id)
вернет вам пустой массив.
Проблема возникает из-за попытки доступа к filteredProduct[0].image
и аналогичным другим свойствам.
Решение здесь используется loadState и визуализировать загрузчик до тех пор, пока данные не станут доступны
Также убедитесь, что при наличии данных filteredProduct
никогда не будет пустым
export class AppProvider extends Component {
state = {
isLoading: true,
products: [],
cart: [],
dispatch: action => this.setState(state => reducer(state, action))
}
fetchData = async () => {
const products = await axios.get('http://127.0.0.1:4000/product/all', { headers: { "Access-Control-Allow-Origin": "*" } })
this.setState({
isLoading: false,
products: products.data
})
}
componentDidMount(){
this.fetchData();
}
render() {
return (
<AppContext.Provider value={this.state}>
{this.props.children}
</AppContext.Provider>
)
}
}
и в productDetails
const ProductDetail = props => {
const [inputValue, setInputValue] = useState(1);
const preventNegative = e => {
const value = e.target.value.replace(/[^\d]/, '');
if (parseInt(value) !== 0) {
setInputValue(value);
}
}
const addToCart = (e, productID) => {
// eslint-disable-next-line array-callback-return
const product = products.filter(p => {
// eslint-disable-next-line eqeqeq
if (p.id == productID) {
return p
}
})
console.log(product);
dispatch({
type: 'ADD_TO_CART',
payload: [product, inputValue]
})
toast.success("The product has been added to cart successfully !", {
position: toast.POSITION.TOP_CENTER,
autoClose: 2500,
transition: Slide
})
}
const { products, dispatch, isLoading } = useContext(AppContext)
if(isLoading) return <div>Loading...</div>
const filteredProduct = products.filter(p => p.id == props.match.params.id)
return (
<React.Fragment>
<main className="mt-5 pt-4">
<div className="container dark-grey-text mt-5">
<div className="row wow fadeIn">
<div className="col-md-6 mb-4">
<img src={filteredProduct[0].image} className="img-fluid" alt="" />
</div>
<div className="col-md-6 mb-4">
<div className="p-4">
<div className="mb-3">
<Badge color="primary">New</Badge>
<Badge color="success">Best seller</Badge>
<Badge color="danger">{filteredProduct[0].discountRate}% OFF</Badge>
</div>
<h2 className="h2">{filteredProduct[0].title}</h2>
<p className="lead">
<span className="mr-1">
<del>${filteredProduct[0].prevPrice}</del>
</span>
<span>${filteredProduct[0].price}</span>
</p>
<p className="text-muted">
{filteredProduct[0].detail}
</p>
<form className="d-flex justify-content-left">
<input
min="1"
onChange={(e) => preventNegative(e)}
value={inputValue}
type="number"
aria-label="Search"
className="form-control"
style={{ width: '100px' }} />
<Button onClick={(e) => addToCart(e, filteredProduct[0].id)} color="primary">Add to Cart <FontAwesomeIcon icon={faShoppingCart} /> </Button>
</form>
</div>
</div>
</div>
</div>
</main>
</React.Fragment>
)
}
export default ProductDetail;