如何在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 请求的同时显示加载。
如有疑问请评论。
我在 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 请求的同时显示加载。
如有疑问请评论。