[英]React: Prevent infinite Loop when calling context-functions in useEffect
In my react app, I am rendering different instances of <Item>
Components and I want them to register/unregister in a Context depending if they are currently mounted or not.在我的 React 应用程序中,我正在渲染<Item>
组件的不同实例,我希望它们在上下文中注册/取消注册,具体取决于它们当前是否已安装。
I am doing this with two Contexts ( ItemContext
provides the registered items, ItemContextSetters
provides the functions to register/unregister).我正在使用两个上下文( ItemContext
提供注册的项目, ItemContextSetters
提供注册/取消注册的功能)。
const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
registerItem: (id, data) => undefined,
unregisterItem: (id) => undefined
});
function ContextController(props) {
const [items, setItems] = useState({});
const unregisterItem = useCallback(
(id) => {
const itemsUpdate = { ...items };
delete itemsUpdate[id];
setItems(itemsUpdate);
},
[items]
);
const registerItem = useCallback(
(id, data) => {
if (items.hasOwnProperty(id) && items[id] === data) {
return;
}
const itemsUpdate = { ...items, [id]: data };
setItems(itemsUpdate);
},
[items]
);
return (
<ItemContext.Provider value={{ items }}>
<ItemContextSetters.Provider value={{ registerItem, unregisterItem }}>
{props.children}
</ItemContextSetters.Provider>
</ItemContext.Provider>
);
}
The <Item>
Components should register themselves when they are mounted or their props.data
changes and unregister when they are unmounted. <Item>
组件应该在挂载或props.data
更改时注册自己,并在卸载时取消注册。 So I thought that could be done very cleanly with useEffect
:所以我认为可以用useEffect
非常干净地useEffect
:
function Item(props) {
const itemContextSetters = useContext(ItemContextSetters);
useEffect(() => {
itemContextSetters.registerItem(props.id, props.data);
return () => {
itemContextSetters.unregisterItem(props.id);
};
}, [itemContextSetters, props.id, props.data]);
...
}
Full example see this codesandbox完整示例请参阅此代码和框
Now, the problem is that this gives me an infinite loop and I don't know how to do it better.现在,问题是这给了我一个无限循环,我不知道如何做得更好。 The loop is happening like this (I believe):循环是这样发生的(我相信):
<Item>
calls registerItem
<Item>
调用registerItem
items
is changed and therefore registerItem
is re-built (because it depends on [items]
在 Context 中, items
被改变,因此registerItem
被重新构建(因为它依赖于[items]
<Item>
because itemContextSetters
has changed and useEffect
is executed again.这会触发<Item>
的更改,因为itemContextSetters
已更改并且再次执行useEffect
。items
in the context这再次更改了上下文中的items
I really can't think of a clean solution that avoids this problem.我真的想不出一个干净的解决方案来避免这个问题。 Am I misusing any hook or the context api?我是否滥用了任何钩子或上下文 api? Can you help me with any general pattern on how write such a register/unregister Context that is called by Components in their useEffect
-body and useEffect
-cleanup?关于如何编写由组件在其useEffect
和useEffect
-cleanup 中调用的注册/取消注册上下文的任何一般模式,您能帮我吗?
Things I'd prefer not to do:我不想做的事情:
<Item>
components from the dom (with {renderFirstBlock &&
) and use something like a state isHidden
instead.避免从 dom 中硬删除<Item>
组件(使用{renderFirstBlock &&
)并使用类似状态isHidden
东西。 In my real App this is currently nothing I can change.在我的真实应用程序中,目前我无法改变。 My goal is to track data
of all existing component instances.我的目标是跟踪data
所有现有组件实例。Thank you!谢谢!
You can make your setters have stable references, as they don't really need to be dependant on items
:你可以让你的 setter 有稳定的引用,因为他们真的不需要依赖items
:
const ItemContext = React.createContext({});
const ItemContextSetters = React.createContext({
registerItem: (id, data) => undefined,
unregisterItem: (id) => undefined
});
function ContextController(props) {
const [items, setItems] = useState({});
const unregisterItem = useCallback(
(id) => {
setItems(currentItems => {
const itemsUpdate = { ...currentItems };
delete itemsUpdate[id];
return itemsUpdate
});
},
[]
);
const registerItem = useCallback(
(id, data) => {
setItems(currentItems => {
if (currentItems.hasOwnProperty(id) && currentItems[id] === data) {
return currentItems;
}
return { ...currentItems, [id]: data }
} );
},
[]
);
const itemsSetters = useMemo(() => ({ registerItem, unregisterItem }), [registerItem, unregisterItem])
return (
<ItemContext.Provider value={{ items }}>
<ItemContextSetters.Provider value={itemsSetters}>
{props.children}
</ItemContextSetters.Provider>
</ItemContext.Provider>
);
}
Now your effect should work as expected现在您的效果应该按预期工作
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.