如何在useEffect清理函数中取消所有订阅和异步任务?

How to cancel all subscriptions and asynchronous tasks in a useEffect cleanup function?

我在 React 功能组件中调用第二个 API 时无法更新状态。第一个 API 调用在 useEffect 内部,第二个 API 调用在用户单击按钮时完成。当第二个 API 调用完成时,React 会抛出此错误“无法在未安装的组件上执行 React 状态更新。这是一个空操作,但它表明您的应用程序中存在内存泄漏。要修复,请取消useEffect 清理函数中的所有订阅和异步任务。” 而且状态没有更新,我想在第二次 API 调用后设置状态。如何解决这个问题?

我的代码:

const AddNewProduct = () => {
  const [productName, setProductName] = useState("");
  const [originalPrice, setOriginalPrice] = useState("");
  const [newPrice, setNewPrice] = useState("");
  const [category, setCategory] = useState("");
  const [description, setDescription] = useState("");
  const [categoriesArray, setCategoriesArray] = useState([]);
  const [isLogin, setIsLogin] = useState([]);
  const [id, setId] = useState("");

  useEffect(() => {
    const getCategoriesData = async () => {
      const Data = await fetchCategoriesApi();
      setIsLogin(Data.data.login);
      setCategoriesArray(Data.data.data);
      console.log(Data);
    };
    getCategoriesData();
  }, []);

  const handleCategoryClick = (id) => {
    setCategory(id);
    console.log(id);
  };

  const handleNextClick = async () => {
    const postApi = "https://fliqapp.xyz/api/seller/products";

    try {
      const post = await axios
        .post(
          postApi,
          {
            product_name: productName,
            product_desc: description,
            product_price: originalPrice,
            product_cat: category,
          },
          {
            headers: {
              Authorization: `Bearer ${localStorage.getItem("token")}`,
            },
          }
        )
        .then((response) => {
          setId(response.data.data.product_id);
          console.log(id);
          console.log(response);
        });
    } catch (error) {
      return error;
    }

    console.log("clicked");
  };

  return (
    <>
      <div className={styles.container}>
        <div className={styles.blank}></div>
        <input
          type="text"
          className={styles.input_field}
          placeholder="Product name*"
          onChange={(e) => setProductName(e.target.value)}
        />
        <input
          type="text"
          className={styles.input_field}
          placeholder="original price*"
          onChange={(e) => setOriginalPrice(e.target.value)}
        />
        <input
          type="text"
          className={styles.input_field}
          placeholder="new price"
          onChange={(e) => setNewPrice(e.target.value)}
        />
        <select
          name="parent category"
          id="parentcategory"
          className={styles.dropdown}
          defaultValue={"DEFAULT"}
          onChange={(e) => handleCategoryClick(e.target.value)}
        >
          <option value="DEFAULT" disabled>
            select category
          </option>
          {isLogin &&
            categoriesArray.map((item, index) => (
              <option value={item.id} key={index}>
                {item.cat_name}
              </option>
            ))}
        </select>
        <textarea
          type="textarea"
          className={styles.input_field}
          placeholder="Description"
          rows="4"
          onChange={(e) => setDescription(e.target.value)}
        />
        <Link
          to={{
            pathname: `/add_image/${id}`,
          }}
          className={styles.btn}
          onClick={handleNextClick}
          disabled
        >
          Next
        </Link>

        <div className={styles.header}>
          <h1 className={styles.heading_normal}>Add new product</h1>
        </div>
      </div>
    </>
  );
};

您需要将 Link 更改为 Button 并手动导航到其他路线,因为路线 /add_image/${id} 中使用的 id 来自第二条 Api打电话。

Reason : because when you click on Link it will fire axios request and change route of your app, thus current component is unmounted and new route component is mounted, after this happens your axios response comeback and try to setState on unmounted component.

// import
import { useHistory } from 'react-router-dom';


// inside component
const history = useHistory();


// click handler
const handleNextClick = async () => {
   // ...axiosrequest
  .then((response) => {
      setId(response.data.data.product_id); // may be not needed now
      const id = response.data.data.product_id;
      history.push(`/add_image/${id}`);
  }
}

// button
<button
  className={styles.btn}
  onClick={handleNextClick}  
>
  Next
</button>

通过这种方式,您只需在从服务器获得正确响应并根据 response ID 更新您的路线后更改一次路线。

为了更好的用户体验,您可以在执行 axios ajax 请求的同时显示加载。

如有疑问请评论。