[英]How to Avoid a useEffect and useCallback Dependency Loop in React.js
I am working on converting a React component from a class component to a functional component with hooks.我正在将 React 组件从 class 组件转换为带有钩子的功能组件。
I want to get a list of group members from my database.我想从我的数据库中获取组成员列表。 I hold the array in component state.
我将数组保存在组件 state 中。
const [members, setMembers] = useState([]);
Once the members are downloaded, I want to get each member's profile picture asynchronously.下载成员后,我想异步获取每个成员的个人资料图片。
1) The component is mounted, and the following useEffect()
is called. 1) 组件被挂载,调用下面的
useEffect()
。 Note the dependency to getMembers
.注意对
getMembers
的依赖。
useEffect(() => {
getMembers();
}, [getMembers]);
2) The useEffect
callback calls the function getMembers()
. 2)
useEffect
回调调用 function getMembers()
。 Note the dependency to getMembersProfilePictures
.请注意对
getMembersProfilePictures
的依赖关系。
const getMembers = useCallback(() => {
fetchMembersFromDatabase()
.then((data) => {
setMembers(data);
getMembersProfilePictures();
})
}, [getMembersProfilePictures]);
3) Once the members are retrieved from the database, the members
state is updated and getMembersProfilePictures()
is called. 3) 一旦从数据库中检索到成员,
members
state 就会更新并getMembersProfilePictures()
。 Note the dependency to members
.注意对
members
的依赖。
const getMembersProfilePictures = useCallback(() => {
for (let i = 0; i < members.length; i++) {
const member = { ...members[i] };
if (member.has_picture) {
firebase
.storage()
.ref()
.child("<childUrl>")
.getDownloadURL()
.then((url) => {
member.picture_url = url;
const membersCopy = [...members];
membersCopy[i] = member;
setMembers(membersCopy);
});
}
}
}, [members]);
Because the useEffect()
depends on getMembers()
, getMembers()
depends on getMembersProfilePictures()
, and getMembersProfilePictures()
depends on members
, as soon as the members
state is updated, the chain of useCallback()
s is re-created, and the useEffect()
is called.因为
useEffect()
依赖于getMembers()
, getMembers()
依赖于getMembersProfilePictures()
,而getMembersProfilePictures()
依赖于members
,所以一旦members
state 更新, useCallback()
链就会重新创建,并且调用useEffect()
。 This becomes an infinite loop of data fetching.这成为数据获取的无限循环。
My current thought is to pass the data retrieved from fetchMembersFromDatabase()
directly to getMembersProfilePictures()
as an argument.我目前的想法是将
fetchMembersFromDatabase()
检索到的数据直接传递给getMembersProfilePictures()
作为参数。 This removes the dependency of members
from getMembersProfilePictures()
, and therefore removes the infinite loop.这从
getMembersProfilePictures()
中删除了members
的依赖关系,因此删除了无限循环。
Ignoring listening to changes in the members list in the database and ignoring caching of members and their respective profile picture, it appears that there are no drawbacks to this solution.忽略监听数据库中成员列表中的更改并忽略成员及其各自的个人资料图片的缓存,似乎该解决方案没有缺点。 I am wondering what other's thoughts are to this solution.
我想知道其他人对此解决方案的想法是什么。 Thanks!
谢谢!
fetchMembersFromDatabase()
in the useCallback
but not using it as a dependency.useCallback
中调用fetchMembersFromDatabase()
但不将其用作依赖项。members
inside getMembersProfilePictures()
which is using members as dependency.getMembersProfilePictures()
中设置members
,它使用成员作为依赖项。 This is causing infinite loop in my opinion.useEffect
without any dependencyuseEffect
useEffect
for member and call the get member images function.useEffect
。 maintain another object for member thumbnails.EDIT: It's become clear you're trying to do too much in one component.编辑:很明显,您试图在一个组件中做太多事情。
What you really want to do is你真正想做的是
Member
components and thenMember
组件,然后Member
components download their own pictures.Member
组件下载自己的图片。 The below is semi-pseudo code, take the async/await stuff with a grain of salt and do it the proper way that works for you:下面是半伪代码,对 async/await 的内容持保留态度,并以适合您的正确方式进行操作:
const MemberList = ({maybeWithAProp}) => {
const [members,setMembers] = useState([]);
useEffect(async () => {
setMembers((await someCallToGetMembers(maybeWithAProp));
},[maybeWithAProp]);
return members.map(m => <Member {...m}/>
}
const Member = ({memberId}) => {
const [pic,setPic] = useState();
useEffect(async () => {
setPic(null); // you're about to download a new one, probably get rid of the old one first
setPic((await fetchPicForMember(memberId));
},[memberId]);
return (...)
}
Original answer:原答案:
Ever consider separating your state?是否考虑过分离您的 state?
const [members, setMembers] = useState([]);
const [membersPics,setMembersPics] = useState([]);
// run on mount only
useEffect(async () => {
const data = await fetchMembersFromDatabase();
setMembers(data);
,[]);
useEffect(async () => {
const pics = await Promise.all(members.map(m => fetchProfilePic(m)));
setMembersPics(pics);
},[members]);
return <SomeComponent members={useMemo(members.map((m,i) => ({...m,url:membersPics[i]})),[members,memberPics])}/>
The major issue with your original code is that you were calling setMembers
twice, which is a big indicator that what you think is one piece of state, is probably better served as being two pieces of state.您的原始代码的主要问题是您调用了两次
setMembers
,这是一个重要指标,表明您认为是 state 的一部分,可能更好地作为两块 state。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.