【问题标题】:Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. because of setState错误:重新渲染过多。 React 限制了渲染的数量以防止无限循环。因为 setState
【发布时间】:2020-07-29 19:36:20
【问题描述】:

setFormData 中可能存在问题,有人可以告诉我我犯了什么错误导致代码渲染。也想知道在状态中存储图片是否有错误。

代码摘要:

  1. 我正在从输入字段添加图像,这由imageSelectedHandler 负责
  2. 我想知道imageUploadHandler 是必需的吗?或者即使不使用我也可以连同图片一起提交表单?
  3. 我还添加了新的输入字段,每次用户点击,添加规范按钮。这是通过 addClick 和 removeClick 处理程序
  4. handleChange 处理程序负责处理各个输入字段。

请帮助某人。

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;

包含@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;

【问题讨论】:

  • 为什么所有sub_category: { ...prevState.sub_category }, 的东西?你可以传播例如{...prevState, product_image: event.target.files[0],}.
  • 如果您将 4 个状态片段从单个 formData 对象拆分为 4 个不同的 useStates,您也会有更好的时间。
  • @AKX 我感谢您的回复,我已经实施了您建议的第一个更改,我还将实施第二个更改,但在实施第二个更改之前,我希望我的代码没有错误,我是仍然得到相同的太多渲染错误。如果您能帮助我找出导致该错误的部分,那就太好了。谢谢。
  • 我很确定你也可以摆脱重新渲染的问题。
  • @AKX ,我也实现了第二个更改,在上面的问题中添加了新代码,但这也没有解决问题,太多的渲染让我头疼。如果您能提供帮助,请查看下面的代码。谢谢你的时间。我还是觉得,代码中还有其他的错误。

标签: reactjs setstate


【解决方案1】:

Ciao,我从未见过在useState 钩子中使用prevState 这样的方法。基本上,当您传播 prevState 时,就像您在说:“为所有状态元素保持相同的值”,然后是逗号,然后是状态的新值列表。

因此,没有必要在当前状态下重新分配 prevState 的相同值(因为它已经有了!)。所以,我认为你应该像这样修改你的代码:

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
   }));
};

关于imageUploadHandler的问题,我认为不需要提交表单。但我不是 100% 确定。

【讨论】:

  • 您好,在您发表评论后,我尝试对其进行搜索,之后对代码进行了一些更改,谢谢您的时间,但同样的问题仍然存在。
猜你喜欢
  • 2020-04-05
  • 2021-10-05
  • 2021-07-16
  • 2021-09-05
  • 2020-09-17
  • 1970-01-01
  • 2022-01-11
  • 1970-01-01
  • 2021-01-22
相关资源
最近更新 更多