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

Posted

技术标签:

【中文标题】如何取消 useEffect 清理函数中的所有订阅和异步任务?【英文标题】:How to cancel all subscriptions and asynchronous tasks in a useEffect cleanup function? 【发布时间】:2021-01-28 13:36:28 【问题描述】:

当我在反应功能组件中调用第二个 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>
    </>
  );
;

【问题讨论】:

【参考方案1】:

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

原因:因为当您单击链接时,它会触发 axios 请求并更改您的应用程序的路由,因此当前组件被卸载并安装了新的路由组件,在这种情况下您的 axios 响应卷土重来并尝试 setState on unmounted组件。

// 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 请求的同时显示加载。

如有任何疑问,请发表评论。

【讨论】:

以上是关于如何取消 useEffect 清理函数中的所有订阅和异步任务?的主要内容,如果未能解决你的问题,请参考以下文章

如何取消firebase的useEffect订阅

删除组件后如何清理react js中的useEffect函数

无法在我的反应本机应用程序中清理订阅

如何使用钩子清理 componentDidUpdate 中的异步函数

如何仅使用 React Native 中的 if 语句返回 useEffect

React useEffect 清理函数中的依赖没有更新