Search code examples
reactjsreact-routercomponentssubmitrefresh

Refresh page after submit form in React


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 enter image description here

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


Solution

  • 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/