在react-query onSuccess事件中更新react状态会导致内存泄漏
我有一个名为 ImageDisplay 的组件。该组件使用自定义反应钩子(useImageUpload)使用反应查询将图像上传到cloudinary。
自定义钩子 useImageUpload 包含一个将上传图像的 url 存储在数组中的状态。自定义挂钩返回一个对象,其中包含图像 urls 数组、上传新图像的函数和 isLoading 布尔值。
图片上传成功。但看起来,这个反应查询片段正在导致内存泄漏。
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
如何更新 onSuccess 事件上的图像数组?
ImageDisplay.js
import React, { useEffect } from "react";
import "styles/ImageDisplay.scss";
import { Field } from "formik";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import useImageUpload from "utils/useImageUpload";
export default function ImageDisplay() {
let { images, uploadImage, uploading } = useImageUpload();
const handleFileChange = (e) => {
const inputFile = e.target.files[0];
uploadImage(inputFile);
};
useEffect(() => {
console.log(images);
}, [images]);
return (
<>
<div className="image-display border mb-3 px-3">
<div className="image-preview text-center">
<h6>Upload product images to attract customers</h6>
{/* <Skeleton width={300} height={300} /> */}
</div>
<hr />
<div className="img-choose border p-2 ">
<div className="options">
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
</div>
</div>
.
</div>
<div className="img-input">
<Field
type="file"
name="productImages"
id="productImages"
onChange={handleFileChange}
accept="image/*"
/>
<h5>Select product images to upload</h5>
</div>
</>
);
}
useImageUpload.js
import axios from "axios";
import { useState } from "react";
import { useMutation } from "react-query";
const useImageUpload = () => {
const [images, setImages] = useState([]);
let { isLoading: uploading, mutateAsync: uploadImage } = useMutation(
async (data) => {
let formData = new FormData();
formData.append("file", data);
formData.append("upload_preset", "uploadPresetHere");
formData.append("cloud_name", "nameHere");
return await axios.post(
"cloudinary_url",
formData,
{
withCredentials: false,
}
);
},
{
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
onError: (error) => {
console.log(error.response);
},
}
);
return { images, uploadImage, uploading };
};
export default useImageUpload;
抱歉,如果我在问题中犯了任何错误。
I have component called ImageDisplay. And that component uses a custom react hook (useImageUpload) to upload images to cloudinary using react-query.
The custom hook useImageUpload contains a state which stores the urls of uploaded images in an array. The custom hook returns an object which contains images urls array, a function to upload new images, and isLoading boolean.
The image is being uploaded successfully. But looks like, this react-query snippet is causing memory leak.
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
How can I update the images array on onSuccess event?
ImageDisplay.js
import React, { useEffect } from "react";
import "styles/ImageDisplay.scss";
import { Field } from "formik";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import useImageUpload from "utils/useImageUpload";
export default function ImageDisplay() {
let { images, uploadImage, uploading } = useImageUpload();
const handleFileChange = (e) => {
const inputFile = e.target.files[0];
uploadImage(inputFile);
};
useEffect(() => {
console.log(images);
}, [images]);
return (
<>
<div className="image-display border mb-3 px-3">
<div className="image-preview text-center">
<h6>Upload product images to attract customers</h6>
{/* <Skeleton width={300} height={300} /> */}
</div>
<hr />
<div className="img-choose border p-2 ">
<div className="options">
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
<Skeleton width={100} height={100} />
</div>
</div>
.
</div>
<div className="img-input">
<Field
type="file"
name="productImages"
id="productImages"
onChange={handleFileChange}
accept="image/*"
/>
<h5>Select product images to upload</h5>
</div>
</>
);
}
useImageUpload.js
import axios from "axios";
import { useState } from "react";
import { useMutation } from "react-query";
const useImageUpload = () => {
const [images, setImages] = useState([]);
let { isLoading: uploading, mutateAsync: uploadImage } = useMutation(
async (data) => {
let formData = new FormData();
formData.append("file", data);
formData.append("upload_preset", "uploadPresetHere");
formData.append("cloud_name", "nameHere");
return await axios.post(
"cloudinary_url",
formData,
{
withCredentials: false,
}
);
},
{
onSuccess: ({ data }) => {
console.log("Image uploaded successfully");
setImages([...images, data.secure_url]);
},
onError: (error) => {
console.log(error.response);
},
}
);
return { images, uploadImage, uploading };
};
export default useImageUpload;
Sorry if I have made any mistake in the question.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这是 React Query 的正常行为:如果使用
useMutation
执行突变的组件卸载,onSuccess
回调仍然会被调用。要修复它,您有多种选择,您可以:
在 ref 中跟踪组件的已安装/已卸载状态:
但不确定它在所有情况下都有效,通常 React 在卸载组件时会删除 refs。
另一个解决方案是将图像存储在 React Query 缓存中。毕竟它的数据来自服务器,所以它是服务器状态并且应该驻留在缓存中:
This is a normal behaviour of React Query : if a component performing a mutation using
useMutation
unmounts, theonSuccess
callback still gets called.To fix it you have several options, you can :
Keep track of the mounted / unmounted state of the component in a ref :
Not sure it works in all cases though, normally React deletes refs when unmounting a component.
Another solution is to store the images in the React Query cache. After all it's data coming from the server, so it's server state and should reside in the cache :
如此答案中所述,即使您的组件卸载,
useMutation
上的回调始终会被调用。但是,您可以在.mutate()
上使用附加回调,因为只有在突变完成时组件仍处于挂载状态时才会调用这些回调。 此处对此进行了记录。此外,此警告是一个红色警告,该警告将在 React 18 中删除,如 React 团队在 React 18 工作组。据称,在已卸载的组件中调用 setState 实际上并不是内存泄漏,而且完全是一件好事,而建议的使用 ref 的解决方法实际上是比原来的问题更糟糕(订阅外部商店时忘记在 useEffects 中取消订阅 - 这可能发生在库中,但对于应用程序开发人员来说不太可能发生)。
As stated in this answer, the callbacks on
useMutation
are always called, even if your component unmounts. However, you can use the additional callback on.mutate()
, as these callbacks will only be called if the component is still mounted when the mutation finishes. This is documented here.Further, this warning is a red hering, and the warning will be removed in React 18, as documented by the react team in the React 18 working group. It is stated that calling
setState
in an already unmounted component is not in fact a memory leak and is a totally fine thing to do, while the proposed workaround with aref
is actually worse than the original problem (which was forgetting to unsubscribe in useEffects when subscribing to external stores - something that can happen in libraries, but is very unlikely to happen for app developers).