TransWikia.com

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. because of setState

Stack Overflow Asked by Sudarshan Mane on January 2, 2021

There might be issue in setFormData, can someone please tell me what mistake i have done that causing rendering in the code. also want to know if there is any mistake in storing image in a state.

Code summary:

  1. I am adding image from input field, and this is taken care by imageSelectedHandler
  2. I want to know is imageUploadHandler required? or even without using I will be able to submit the form along with the image?
  3. I am also adding new input fields, everytime user clicks, add specification button. this is by addClick and removeClick handler
  4. handleChange handler take care of indivisual Input Flield.

Please help someone.

import React, { useState } from "react";
import FormInput from "../Forminput/forminput";
import CustomButton from "../Custombutton/custombutton";
import axios from "axios";

const ProductUpload = () => {
  const [formData, setFormData] = useState({
    sub_category: null,
    product_name: null,
    product_image: null,
    product_specs: [{ specification: "", specvalue: "" }],
  });

  const { sub_category, product_name, product_image, product_specs } = formData;

  const imageSelectedHandler = (event) => {
    setFormData((prevState) => ({
      product_image: event.target.files[0],
      sub_category: { ...prevState.sub_category },
      product_name: { ...prevState.product_name },
      product_specs: [
        ...prevState.product_specs,
        { specification: "", specvalue: "" },
      ],
    }));
  };

  const imageUploadHandler = () => {
    const fd = new FormData();
    fd.append("product_image", product_image, product_image.name); //.name is Imp as name is property of file
  };
  const handleChange = (i, e) => {
    const { name, value } = e.target;
    product_specs[i] = { ...product_specs[i], [name]: value };
    setFormData({ product_specs }); //TO BE CHECKED
  };
  //to add extra input field
  const addClick = () => {
    setFormData((prevState) => ({
      sub_category: { ...prevState.sub_category },
      product_image: { ...prevState.image },
      product_name: { ...prevState.product_name },
      product_specs: [
        ...prevState.product_specs,
        { specification: "", specvalue: "" },
      ],
    }));
  };
  //to remove extra input field
  const removeClick = (i) => {
    product_specs.splice(i, 1);
    setFormData((prevState) => {
      return {
        sub_category: { ...prevState.sub_category },
        product_image: { ...prevState.image },
        product_name: { ...prevState.product_name },
        product_specs: [
          ...prevState.product_specs,
          { specification: "", specvalue: "" },
        ],
      };
    });
  };
  const handleSubmit = async (event) => {
    event.preventDefault();
    const newProduct = {
      sub_category,
      product_name,
      product_image,
      product_specs,
    };
    try {
      const config = {
        headers: {
          "Content-Type": "application/json",
        },
      };
      const body = JSON.stringify(newProduct);
      const res = await axios.post("/api/product", body, config);
      console.log(res.data);
    } catch (error) {
      console.error(error.response.data);
    }
  };
  const createUI = () => {
    return product_specs.map((el, i) => (
      <div key={i} className="inputgroup">
        <FormInput
          type="text"
          name="specification"
          handleChange={handleChange}
          value={el.specification}
          label="specification"
          required
          customwidth="300px"
        ></FormInput>
        <FormInput
          type="text"
          name="specvalue"
          handleChange={handleChange}
          value={el.specvalue}
          label="specification values seperated by quomas"
          required
        ></FormInput>
        <CustomButton
          onClick={removeClick(i)}
          type="button"
          value="remove"
          style={{ margin: "12px" }}
        >
          Remove
        </CustomButton>
      </div>
    ));
  };
  return (
    <div className="container">
      <form
        action="/upload"
        method="post"
        className="form"
        onSubmit={handleSubmit}
        encType="multipart/form-data"
      >
        <h3 style={{ color: "roboto, sans-serif" }}>
          Add new product to the database
        </h3>
        <div
          style={{
            display: "flex",
            height: "200px",
            width: "200px",
            border: "2px solid #DADCE0",
            borderRadius: "6px",
            position: "relative",
          }}
        >
          <input
            style={{ display: "none" }}
            type="file"
            accept="image/*"
            onChange={imageSelectedHandler}
            ref={(fileInput) => (this.fileInput = fileInput)}
            multiple={false}
            name="product_image"
          />
          <CustomButton onClick={() => this.fileInput.click()}>
            Select Image
          </CustomButton>
          <CustomButton onClick={imageUploadHandler}>Upload</CustomButton>

          {/*as per brad- type = "submit" value="submit"  this should not be used, file should upload with the form submit */}
          <div>
            <img
              style={{
                width: "100%",
                height: "100%",
              }}
              alt="#"
            />
          </div>
        </div>

        {createUI()}
        <div>
          <CustomButton
            onClick={addClick}
            type="button"
            style={{ margin: "14px" }}
          >
            Add More Fields
          </CustomButton>
          <CustomButton type="submit" style={{ margin: "12px" }}>
            Upload Product
          </CustomButton>
        </div>
      </form>
    </div>
  );
};

export default ProductUpload;

code with the changes suggested by @AKX

import React, { useState } from "react";
import FormInput from "../Forminput/forminput";
import CustomButton from "../Custombutton/custombutton";
import axios from "axios";

const ProductUpload = () => {
  const [sub_category, setSub_category] = useState({
    sub_category: "",
  });

  const [product_name, setProduct_name] = useState({
    product_name: "",
  });

  const [product_image, setProduct_image] = useState({
    product_image: "",
  });

  const [product_specs, setProduct_specs] = useState({
    product_specs: [{ specification: "", specvalue: "" }],
  });

  const imageSelectedHandler = (event) => {
    setProduct_image({ product_image: event.target.files[0] });
  };

  // const imageUploadHandler = () => {
  //   const fd = new FormData();
  //   fd.append("product_image", product_image, product_image.name); //.name is Imp as name is property of file
  // };

  const handleChange = (i, e) => {
    const { name, value } = e.target;
    product_specs[i] = { ...product_specs[i], [name]: value };
    setProduct_specs({ product_specs }); //TO BE CHECKED
  };
  //to add extra input field
  const addClick = () => {
    setProduct_specs({
      product_specs: [...product_specs, { specification: "", specvalue: "" }],
    });
  };
  //to remove extra input field
  const removeClick = (i) => {
    [product_specs].splice(i, 1);
    setProduct_specs({
      product_specs: [product_specs],
    });
  };
  const handleSubmit = async (event) => {
    event.preventDefault();
    const newProduct = {
      sub_category,
      product_name,
      product_image,
      product_specs,
    };
    try {
      const config = {
        headers: {
          "Content-Type": "application/json",
        },
      };
      const body = JSON.stringify(newProduct);
      const res = await axios.post("/api/product", body, config);
      console.log(res.data);
    } catch (error) {
      console.error(error.response.data);
    }
  };
  const createUI = () => {
    return [product_specs].map((el, i) => (
      <div key={i} className="inputgroup">
        <FormInput
          type="text"
          name="specification"
          handleChange={handleChange}
          value={el.specification}
          label="specification"
          required
          customwidth="300px"
        ></FormInput>
        <FormInput
          type="text"
          name="specvalue"
          handleChange={handleChange}
          value={el.specvalue}
          label="specification values seperated by quomas"
          required
        ></FormInput>
        <CustomButton
          onClick={removeClick(i)}
          type="button"
          value="remove"
          style={{ margin: "12px" }}
        >
          Remove
        </CustomButton>
      </div>
    ));
  };
  return (
    <div className="container">
      <form
        action="/upload"
        method="post"
        className="form"
        onSubmit={handleSubmit}
        encType="multipart/form-data"
      >
        <h3 style={{ color: "roboto, sans-serif" }}>
          Add new product to the database
        </h3>
        <div
          style={{
            display: "flex",
            height: "200px",
            width: "200px",
            border: "2px solid #DADCE0",
            borderRadius: "6px",
            position: "relative",
          }}
        >
          <input
            style={{ display: "none" }}
            type="file"
            accept="image/*"
            onChange={imageSelectedHandler}
            ref={(fileInput) => (this.fileInput = fileInput)}
            multiple={false}
            name="product_image"
          />
          <CustomButton onClick={() => this.fileInput.click()}>
            Select Image
          </CustomButton>
          <CustomButton
          //  onClick={imageUploadHandler}
          >
            Upload
          </CustomButton>

          {/*as per brad- type = "submit" value="submit"  this should not be used, file should upload with the form submit */}
          <div>
            <img
              style={{
                width: "100%",
                height: "100%",
              }}
              alt="#"
            />
          </div>
        </div>
        <FormInput
          type="text"
          name="sub_category"
          handleChange={handleChange}
          value={sub_category}
          label="select from subcategories"
          required
        ></FormInput>
        <FormInput
          type="text"
          name="product_name"
          handleChange={handleChange}
          value={product_name}
          label="product name"
          required
        ></FormInput>
        {createUI()}
        <div>
          <CustomButton
            onClick={addClick}
            type="button"
            style={{ margin: "14px" }}
          >
            Add More Fields
          </CustomButton>
          <CustomButton type="submit" style={{ margin: "12px" }}>
            Upload Product
          </CustomButton>
        </div>
      </form>
    </div>
  );
};

export default ProductUpload;

One Answer

Ciao, I have never seen an use of prevState in useState hook like that. Basically, when you spread prevState it's like you saying: "keep the same values for all the state elements" then comma, then a list of new values for the state.

So, it's not necessary to re-assign the same value of prevState in current state (because it already have!). So, I think you should modify your code like this:

const imageSelectedHandler = (event) => {
  let new_product_specs = formData.product_specs;
  new_product_specs.push({ specification: "", specvalue: "" });
  setFormData((prevState) => ({
     ...prevState,
     product_image: event.target.files[0],
     product_specs: new_product_specs
   }));
};

For the question about imageUploadHandler, I don't think is required to submit form. But I'm not 100% sure.

Answered by Giovanni Esposito on January 2, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP