0

Im receving some products on props in the OrderContent component to use them in a select component, when I select the product in the select it renders Summary and Product components, in those components I can choose the quantity and with that I can calculate the total all back on the OrderContent Component, the problem is when im trying to use the OnChange in the input type (on Product component), useEffect (inside is the function that calculates the total in the state) doesnt trigger but it does if I add a product from the state or remove it.

import React, { Fragment, useState, useEffect } from "react";
import Select from "react-select";
import Animated from "react-select/lib/animated";
import Summary from './Summary';

function OrderContent({ products }) {

  const [productsSelected,setProductsSelected] = useState([]);
  const [total,setTotal] = useState(0);

  useEffect(() => {
    updateTotal()
  }, [productsSelected]);

  const selectProduct = (prod)=>{
    setProductsSelected(prod)
  }  

  const updateQuantity = (val,index)=>{
    const tempProds = productsSelected;
    tempProds[index].quantity= Number(val);
    setProductsSelected(tempProds)
  }  

  const deleteProduct = (id) =>{
    const tempProds = productsSelected;
    const remProds = tempProds.filter((p)=> p.id !== id );
    setProductsSelected(remProds);
    }

  const updateTotal = () =>{
    const tempProds = productsSelected;
    if(tempProds.length === 0){
      setTotal(0)
      return;
    }
    let newTotal = 0;
    tempProds.map((p)=>{
      const q = p.quantity ? p.quantity : 0;
      newTotal = newTotal + (q * p.price)
    })
    setTotal(newTotal)
  }

  return (
    <Fragment>
      <h2 className="text-center mb-5">Select Products</h2>
      <Select
        onChange={selectProduct}
        options={products}
        isMulti={true}
        components={Animated()}
        placeholder={"Select products"}
        getOptionValue={options => options.id}
        getOptionLabel={options => options.name}
        value={productsSelected}
      />
      <Summary
        products={productsSelected}
        updateQuantity={updateQuantity}
        deleteProduct = {deleteProduct}
      />
      <p className="font-weight-bold float-right mt-3">
      Total:
        <span className="font-weight-normal">
          ${total}
        </span>
      </p>
    </Fragment>
  );
}

export default OrderContent;


import React, {Fragment} from 'react';
import Product from './Product';

function Summary({products,updateQuantity,deleteProduct}) {

    if(products.length === 0) return null;


    return (
        <Fragment>
        <h2 className="text-center my-5">Summary and Quantities</h2>
        <table className="table">
            <thead className="bg-success text-light">
                <tr className="font-weight-bold">
                    <th>Product</th>
                    <th>Price</th>
                    <th>Inventory</th>
                    <th>Quantity</th>
                    <th>Delete</th>
                </tr>
            </thead>
            <tbody>
                {products.map((p,index)=>{
                    return (<Product
                            key={p.id}
                            id={p.id}
                            product={p}
                            index={index}
                            updateQuantity={updateQuantity}
                            deleteProduct={deleteProduct}
                        />)
                })}
            </tbody>
        </table>
        </Fragment>
    )
}

export default Summary



import React, { Fragment } from "react";

function Product({ product, updateQuantity, index, deleteProduct }) {
  return (
    <Fragment>
      <tr>
        <td>{product.name}</td>
        <td>${product.price}</td>
        <td>{product.stock}</td>
        <td>
          <input
            type="number"
            className="form-control"
            onChange={e => updateQuantity(e.target.value, index)}
          />
        </td>
        <td>
          <button type="button" className="btn btn-danger font-weight-bold" onClick={e=> deleteProduct(product.id)}>
            &times; Delete
          </button>
        </td>
      </tr>
    </Fragment>
  );
}

export default Product;
R.Romero
  • 161
  • 1
  • 4
  • 15

2 Answers2

0

updateQuantity is mutating state. This means that react will see that you've tried to update state with the same object reference and the re-render will be skipped, meaning no useEffect triggers.

Change it to this to create a new array with new nested objects:

const updateQuantity = (val,index)=>{
  const tempProds = [...productsSelected.map(val => {...val})];
  tempProds[index].quantity= Number(val);
  setProductsSelected(tempProds)
}

deleteProduct doesn't mutate because filter returns a new array. But setting the tempProds is completely unnecessary.

updateTotal also mutates state, but only its nested objects. So this still needs to be fixed, but will probably not cause the same re-render issue.

Based on the use of const tempProds = productsSelected in several places, I think you should do some research on how JavaScript objects are assigned and referenced. There's plenty of resources out there, but I wrote a pretty detailed explanation as part of this answer.

Brian Thompson
  • 13,263
  • 4
  • 23
  • 43
0

If productsSelected is the same array then useEffect can't detect the change because it's always pointing to the same object

const selectProduct = (prod)=>{
  setProductsSelected([...prod])
}  

To force the product selected to be a new array

izambl
  • 629
  • 3
  • 9