When I change the price of my program in the input and I click on the update button, all is well the price changes but when I refresh my page I have an error that appears and even when I do not modify the price and I just refresh the page this error appears and I don't understand why
Here is the screenshot of the error and you will find my code below
App
import React, { useState, useEffect } from 'react';
import './css/App.css';
import './css/Products.css';
import axios from 'axios';
import Navigation from './Components/Navigation';
import Products from './Components/Products';
import {BrowserRouter as Router, Route, Switch,} from 'react-router-dom';
import ProductsDetails from './Components/ProductsDetails';
export default function App() {
const [productsData, setProductsData] = useState([]);
useEffect(() => {
axios.get("https://fakestoreapi.com/products?limit=7").then((res) => {
setProductsData(res.data);
});
}, []);
const updatePrice = (id, price) => {
setProductsData((productsData) =>
productsData.map((product) =>
product.id === Number(id)
? {
...product,
price: Number(price)
}
: product
)
);
};
return (
<div className="App">
<Router>
<Navigation/>
<Switch>
<Route
path="/products-details/:id"
render={(props) => (
<ProductsDetails
products={productsData}
updatePrice={updatePrice}
{...props}
/>
)}
/>
<Route path="/">
<Products products={productsData}
/>
</Route>
</Switch>
</Router>
</div>
);
}
Products
import React, { Component } from 'react';
import '../css/Products.css';
import './ProductsDetails'
import {Link} from 'react-router-dom'
export default class Products extends Component {
render() {
const listsProducts = this.props.products.map((listProduct) => {
return (
<tbody className="products__body" key={listProduct.id}>
<tr>
<td><Link to={{pathname: "/products-details/" + listProduct.id}}>{listProduct.title}</Link></td>
<td><p className={`${listProduct.category==="men's clothing" ? "category__orange" : "category__green"}`}>{listProduct.category}</p></td>
<td>{Number(listProduct.price).toFixed(2)}</td>
<td>
{Number(listProduct.price * 1.2).toFixed(2)} €
</td>
</tr>
</tbody>
);
});
return (
<main className="products">
<h1 className="products__title">Products management</h1>
<table cellSpacing="0">
<thead className="products__head">
<tr>
<th className="table--title">Product name</th>
<th className="table--title">Category</th>
<th className="table--title">Price</th>
<th className="table--title">Price (including VAT)</th>
</tr>
</thead>
{listsProducts}
</table>
</main>
);
}
}
ProductsDetails
import React, { Component } from 'react'
import '../css/ProductsDetails.css'
import {AiOutlineArrowLeft} from "react-icons/ai";
import {Link} from 'react-router-dom'
export default class ProductsDetails extends Component {
constructor(props) {
super(props);
// ajout de deux états l'id qu'on récupére de l'API et le prix qui vaut 0
this.state = {
id: this.props.match.params.id,
price: 0
};
}
// mise en place d'une méthode qui permet de modifier la valeur dans l'input texte
updatePrice = (e) => {
console.log(e);
this.setState({
price: e.target.value
});
};
/*
mise en place d'une méthode qui permet au clique du bouton de changer le prix
grâce à la méthode "updatePrice ainsi que l'id et l'état du prix du produit
*/
submitHandler = (e) => {
// permet au clique du bouton que concerver localement la valeur modifiée
localStorage.setItem('price', this.state.price)
e.preventDefault();
const {
match: {
params: { id }
}
} = this.props;
this.props.updatePrice(id, this.state.price);
};
// permet de garder la valeur modifiée au refresh de la page
componentDidMount() {
const price = localStorage.getItem('price')
this.setState({price});
}
render() {
const {
match: {
params: { id }
},
products
} = this.props;
// Ajout d'une variable qui récupére le premier id trouvé dans le tableau "products"
const listProduct = products.find((product) => product.id === Number(id));
return (
<div className="products__details">
<Link to="/">
<AiOutlineArrowLeft className="nav__arrow" />
</Link>
<h1 className="details__title">{listProduct.title}</h1>
<div className="details__align--desk">
<div className="details__img">
<img
className="product__img"
src={listProduct.image}
alt="Affichage du produit"
/>
</div>
<div className="products__align--desk">
<h2 className="product__title">Description</h2>
<p className="product__description">{listProduct.description}</p>
<h2 className="product__title">Price</h2>
<form className="form__price" onSubmit={this.submitHandler}>
<input
name="price"
className="input__price"
type="text"
defaultValue={Number(listProduct.price).toFixed(2)}
onChange={this.updatePrice}
/>
<p>
Price (including VAT):{" "}
{Number(listProduct.price * 1.2).toFixed(2)} €
</p>
<br />
<input
className="btn__update"
type="submit"
value="Update product"
/>
</form>
</div>
<div className="category__align--desk">
<h2 className="product__title">Category</h2>
<p className={`${listProduct.category==="men's clothing" ? "category__orange" : "category__green"} product__category`}>{listProduct.category}</p>
</div>
</div>
</div>
);
}
}
I thank you in advance for your explanations
I suspect its because you're not covering a state when listProduct
is null
. When it loads, it immediately renders, as it's getting your product data, so products
can be empty, and the .find()
would return a null
, then you're trying to render listProduct.title
while it's null
. Ideally, your render should check for listProduct
being available or not.
Even better, have a few states of your component, like loading data, showing data, no data.
<Card>
{ dataStatus === "Loaded" &&
<Card.Title>{person.Name}</Card.Title>
}
{ dataStatus === "Loading" &&
<Card.Loader>Loading...</Card.Loader>
}
{ dataStatus === "Error" &&
<Card.Error>Sorry, we had an oopsie...</Card.Error>
}
{ dataStatus === "Empty" &&
<Card.Empty>Looks like we're missing something...</Card.Empty>
}
</Card>
Further explanation and code examples here: https://davidlozzi.com/2021/05/14/keeping-react-components-state-top-of-mind/