Search code examples
javascriptreactjsreact-hooksuse-statesetstate

When to use setState hook in React


I have a component where I load a list of products with their information.

  useEffect(() => {
    getProducts().then((products) => {
      if (products) {
        setLoaded(true);
        setProducts([...products]);
      }
    });
  }, []);

Then inside the render, I go over the products array and calculate the products' total. Total is also a state within the component.

 {products.length >= 0 &&
              products.map((product) => {
                total += itemQuantity[product.id]
                  ? itemQuantity[product.id] * product.price
                  : 0;
                return (
                  <Product
                    key={product.id}
                    id={product.id}
                    name={product.name}
                    availableCount={product.availableCount}
                    price={product.price}
                    orderedQuantity={
                      itemQuantity[product.id] ? itemQuantity[product.id] : 0
                    }
                    total={
                      itemQuantity[product.id]
                        ? itemQuantity[product.id] * product.price
                        : 0.0
                    }
                    increace={increaseProductuantity}
                    decrease={decreaseProductuantity}
                  />
                );
              })} 

As you can see, in the first line of the map function, I'm adding the price*quantity of each iterated product to the total.

Now if I use SetTotal here, it will cause an infinite loop because the product gets rendered every time and every time the total changes. so I changed the total directly. and then displayed it like this :

<p>Total: $ {total > 1000 ? 0.9 * total : total} </p>

Is this pattern correct usage of the total state or do I must have to use the setState hook every time I update the state? I just want to utilize the iteration for each product to calculate the total. If there is an efficient way of achieving this please share.

Btw here is the full component code:

const Checkout = () => {
  const [loaded, setLoaded] = useState(false);
  const [products, setProducts] = useState([]);
  const [itemQuantity, setItemQuantity] = useState([]);
  let [total, setTotal] = useState(0);

  useEffect(() => {
    getProducts().then((products) => {
      if (products) {
        setLoaded(true);
        setProducts([...products]);
      }
    });
  }, []);

  const increaseProductuantity = (productID) => {
    let quantit = itemQuantity[productID];
 
    quantit
      ? setItemQuantity({ ...itemQuantity, [productID]: ++quantit })
      : setItemQuantity({ ...itemQuantity, [productID]: 1 });
  };
  const decreaseProductuantity = (productID) => {
    let quantit = itemQuantity[productID];
    if (quantit) setItemQuantity({ ...itemQuantity, [productID]: --quantit });
  };

  return (
    <div>
      <header className={styles.header}>
        <h1>Electro World</h1>
      </header>
      <main>
        {!loaded && <LoadingIcon />}
        <table className={styles.table}>
          <thead>
            <tr>
              <th>Product ID</th>
              <th>Product Name</th>
              <th># Available</th>
              <th>Price</th>
              <th>Quantity</th>
              <th>Total</th>
              <th></th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {products.length >= 0 &&
              products.map((product) => {
                total += itemQuantity[product.id]
                  ? itemQuantity[product.id] * product.price
                  : 0;
                return (
                  <Product
                    key={product.id}
                    id={product.id}
                    name={product.name}
                    availableCount={product.availableCount}
                    price={product.price}
                    orderedQuantity={
                      itemQuantity[product.id] ? itemQuantity[product.id] : 0
                    }
                    total={
                      itemQuantity[product.id]
                        ? itemQuantity[product.id] * product.price
                        : 0.0
                    }
                    increace={increaseProductuantity}
                    decrease={decreaseProductuantity}
                  />
                );
              })}
          </tbody>
        </table>
        <h2>Order summary</h2>
        {total > 1000 ? <p>Discount: $ ${0.1 * total} </p> : ""}
        <p>Total: $ {total > 1000 ? 0.9 * total : total} </p>
      </main>
    </div>
  );
};

Solution

  • First of all, read this https://beta.reactjs.org/learn/choosing-the-state-structure

    Avoid duplication in state. When the same data is duplicated between multiple state variables, or within nested objects, it is difficult to keep them in sync. Reduce duplication when you can.

    In your example, total and itemQuantity can be inferred and shouldn't be a separate states.

    If calculating this is expensive, use useMemo hook