[英]How do I use an updated value in useEffect
I'm new to Javascript and I'm currently experimenting with the Demo application out of the Docker getting-started tutorial .我是 Javascript 新手,我目前正在 Docker入门教程中尝试使用 Demo 应用程序。 The application is a simple Todo list where you can add items and remove them.该应用程序是一个简单的待办事项列表,您可以在其中添加和删除项目。
I'm trying to update the list on every instance of the page without having to reload the page.我正在尝试更新页面的每个实例上的列表,而无需重新加载页面。 I've managed to edit the node express server so that it sends updates via Server-sent events.我设法编辑了节点快速服务器,以便它通过服务器发送的事件发送更新。
The problem: The frontend uses React.问题:前端使用 React。 The data of the currently displayer items is contained in the ìtems
array.当前显示项的数据包含在ìtems
数组中。 onNewItem
adds items to that array. onNewItem
将项目添加到该数组。 However when onNewItem
is called from onmessage
the items array is null even though it's not null when onNewItem
is called from other React components.但是,当从onmessage
调用onNewItem
onmessage
,即使从其他 React 组件调用onNewItem
时它不为 null,items 数组也是空的。 How can I access the initialized version of the items
array?如何访问items
数组的初始化版本? (It gets initialized by the 1. useEffect which fetches items from the server) (它由从服务器获取项目的 1.useEffect 初始化)
Below is a part of the code下面是部分代码
function TodoListCard() {
const [items, setItems] = React.useState(null);
const [ listening, setListening ] = React.useState(false);
React.useEffect(() => {
fetch('/items')
.then(r => r.json())
.then(setItems);
}, []);
React.useEffect( () => {
if (!listening) {
const events = new EventSource('/events/subscribe');
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
switch (parsedData.type) {
case "add":
var newItem = {id: parsedData.id, name: parsedData.name, completed: parsedData.completed};
onNewItem(newItem);
break;
default:
break;
}
};
setListening(true);
}
}, [listening]);
const onNewItem = React.useCallback(
newItem => {
if (items.some(e => e.id === newItem.id)){return;}
setItems([...items, newItem]);
},
[items],
);
Let's start of by why things are going wrong.让我们从为什么会出错开始。 The issue is that when you call onNewItem(newItem)
you are using an outdated reference to the onNewItem
.问题是,当您调用onNewItem(newItem)
您使用的是对onNewItem
的过时引用。 For this reason items
within the function will still be set to the initial value.为此,函数内的items
仍将设置为初始值。
You partially solved this by providing an dependency array to React.useCallback
.您通过向React.useCallback
提供依赖数组部分解决了这个问题。 This will update onNewItem
when a new value of items
is available.当items
的新值可用时,这将更新onNewItem
。 However since React.useEffect
does not list onNewItem
as a dependency it keeps using the old version of onNewItem
.然而,由于React.useEffect
没有将onNewItem
作为依赖项列出,所以它一直使用旧版本的onNewItem
。
With this being said you might consider adding onNewItem
, to the dependency array of React.useEffect
.随着这是说你可以考虑添加onNewItem
,要的依赖阵列React.useEffect
。 Although this is the correct action, just adding this to dependency array is not enough.尽管这是正确的操作,但仅将其添加到依赖项数组是不够的。
What is the problem you get when you add onNewItem
to the depency array of React.useEffect
?将onNewItem
添加到 React.useEffect 的依赖数组时会React.useEffect
什么问题? There is no cleanup function, so you will subscribe to the channel multiple times with different onmessage
handlers (different versions of onNewItem
).没有清理功能,因此您将使用不同的onmessage
处理程序(不同版本的onNewItem
)多次订阅频道。
So taking all the above into account a solution might look something like this:因此,考虑到以上所有内容,解决方案可能如下所示:
function TodoListCard() {
const [items, setItems] = React.useState(null);
const [events, setEvents] = React.useState(null);
React.useEffect(() => {
const pEvents = fetch('/items')
.then(r => r.json())
.then(setItems)
.then(() => new EventSource('/events/subscribe'));
pEvents.then(setEvents);
return () => pEvents.then(events => events.close());
}, []);
React.useEffect(() => {
if (!events) return;
events.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
switch (parsedData.type) {
case "add":
var newItem = {
id: parsedData.id,
name: parsedData.name,
completed: parsedData.completed
};
onNewItem(newItem);
break;
default:
break;
}
};
}, [events, onNewItem]);
const onNewItem = React.useCallback(newItem => {
const isPresent = items.some(item => item.id === newItem.id);
if (isPresent) return;
setItems([...items, newItem]);
}, [items]);
return (
// ...
);
}
I've moved the EventSource
creation inside the first React.useEffect
since that only needs to happen once the component is mounted (and needs to close the connection on unmount).我已经在第一个React.useEffect
移动了EventSource
创建,因为只有在安装组件后才需要发生(并且需要在卸载时关闭连接)。 An empty dependency array will only call the function on mount, and calls the cleanup function on unmount.一个空的依赖数组只会在挂载时调用函数,在卸载时调用清理函数。
The second React.useEffect
now has the dependency array [events, onNewItem]
, because when events
is set the onmessage
handler needs to be attached.第二个React.useEffect
现在有依赖数组[events, onNewItem]
,因为当设置events
时需要附加onmessage
处理程序。 And if the onNewItem
callback updates to a new version you should attach it as the new onmessage
handler (replacing the old handler).如果onNewItem
回调更新为新版本,您应该将其附加为新的onmessage
处理程序(替换旧的处理程序)。 This doesn't need a cleanup function anymore since, opening and closing events
is already handled.这不再需要清理功能,因为已经处理了打开和关闭events
。
Although the above should do the job.虽然上面应该做的工作。 If managing a specific state is becoming more complicated it might be better to opt for useReducer
instead of useState
.如果管理特定状态变得越来越复杂,最好选择useReducer
而不是useState
。
function reducer(items, action) {
switch (action.type) {
case "add":
const isPresent = items.some(item => item.id == action.item.id);
if (isPresent) return items;
return [...items, action.item];
case "replace all":
return action.items;
case "complete": // <- unused example case
return items.map(item => {
if (item.id != action.id) return item;
return {...item, completed: true};
});
// ...
default: // silently ignore unsupported operations
return items;
}
}
function TodoListCard() {
const [items, dispatch] = React.useReducer(reducer, null);
React.useEffect(() => {
const pEvents = fetch('/items')
.then(r => r.json())
.then(items => dispatch({type: "replace all", items}))
.then(() => new EventSource('/events/subscribe'));
pEvents.then(events => {
events.onmessage = (event) => {
const {type, ...item} = JSON.parse(event.data);
dispatch({type, item});
};
});
return () => pEvents.then(events => events.close());
}, []);
// if you still need onNewItem for your render:
const onNewItem = React.useCallback(item => {
dispatch({type: "add", item});
}, []);
return (
// ...
);
}
The above extracts all the items
management logic into a "reducer" function.以上将所有的items
管理逻辑提取到一个“reducer”函数中。 The dispatch
function returned by useReducer
is guaranteed to be stable by React, so you can omit it from dependency arrays (but you don't have to). useReducer
返回的dispatch
函数被 React 保证是稳定的,所以你可以从依赖数组中省略它(但你不必)。
The error that you have done is you are not getting any data from your api.The setItems
in your first useEffect
won't work.你曾经做过的错误是你没有得到从api.The任何数据setItems
在第一useEffect
将无法正常工作。
Wrong Way:错误道:
React.useEffect(() => {
fetch('/items')
.then(r => r.json())
.then(setItems);
}, []);
Right Way:正确的方法:
useEffect(() => {
fetch('/items')
.then(r => r.json())
.then((result) => {
setItems(result.items)
});
}, []);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.