英文:
The state called from an event handler function does not contain the expected value
问题
我明白你的问题。在你的代码中,当"newNotification"事件触发时,调用了showCurrent
函数,但无论实际的current
值是多少,日志总是显示"showCurrent is running and current = 0"。这似乎是一个问题。
可能的原因是showCurrent
函数在useEffect
中注册为事件处理程序,而该函数捕获了current
的值,但它捕获的是事件处理程序函数创建时的current
值,而不是事件触发时的最新值。为了解决这个问题,你可以使用useRef
来捕获current
的最新值,如下所示:
import { useContext, useEffect, useState, useRef } from "react";
export const DashboardNavbar = (props) => {
const socket = useContext(WebsocketContext);
const [current, setCurrent] = useState(0);
const currentRef = useRef(current);
console.log("dashboard is rerendering...");
const showCurrent = () => {
console.log("showCurrent is running and current =", currentRef.current);
};
const incrementCurrent = () => {
setCurrent((prev) => prev + 1);
};
useEffect(() => {
socket.on("newNotification", (payload) => {
// ...
showCurrent();
});
return () => {
// Unregistering Events...
socket.off("connect");
socket.off("newNotification");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
// Update the currentRef whenever current changes
currentRef.current = current;
}, [current]);
return (
<>
<button
onClick={() => {
showCurrent();
}}
>
show{" "}
</button>
<button
onClick={() => {
incrementCurrent();
}}
>
increment
</button>
</>
);
}
export default DashboardNavbar;
通过这种方式,currentRef
将会反映current
的最新值,而不是事件处理程序函数创建时的值,这应该解决你遇到的问题。
英文:
I have this component which I minimize as much as possible while debugging and can finally explain the behavior afterwards, it was hard to find the problem because I had some complex logic there, anyway:
import { useContext, useEffect, useState } from "react";
export const DashboardNavbar = (props) => {
const socket = useContext(WebsocketContext);
const [current, setCurrent] = useState(0);
console.log("dashboard is rerendering...")
const showCurrent = () => {
console.log("showCurrent is running and current = ", current);
};
const incrementCurent = () => {
setCurrent((prev) => prev + 1);
};
useEffect(() => {
socket.on("newNotification", (payload) => {
// ...
showCurrent();
});
return () => {
// Unregistering Events...
socket.off("connect");
socket.off("newNotification");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<button
onClick={() => {
showCurrent();
}}
>
show{" "}
</button>
<button
onClick={() => {
incrementCurent();
}}
>
increment
</button>
</>
);
}
export default DashboardNavbar;
As you can see I have a button to increment current
state and when this happen the component rerenders so I see the logs
dashboard is rerendering...
and then when I click the "show" button, i.e call showCurrent
I am able to see the logs
showCurrent is running and current = N
where N is the expected value of current
, so far so good, the problem is when "newNotification" is triggered so its event handler function runs, i.e showCurrent
, the same function, however in this case, I always see the logs
showCurrent is running and current = 0
so no matter what it is the real-time value of current
, it is always equal to 0
there.
答案1
得分: 0
I understood why this was happening.
In most cases, we use event handlers inside JSX as onClick
, onChange
, etc. So each time the component rerenders, those functions are recreated, therefore, we don't face this kind of issue.
Here, this one is different. I had useEffect
with an empty dependency array, so it will fire only once after the first render of the component. So the event handler will always use the same referenced showCurrent
version when that useEffect
ran, and since at that time current
was equal to 0
, then it will always be equal to 0
there.
I figured out that I need to make the event handler references to the newest created showCurrent
function each time current
is updated, so then access the newest/expected value of current
.
That being said, a straightforward solution is to include current
in the dependency array of the effect; this will make it fire each time current
changes value and does what we want it to:
useEffect(() => {
socket.on("newNotification", (payload) => {
//...
});
return () => {
socket.off("connect");
socket.off("newNotification");
};
}, [current]); // include "current"
Here is a sandbox example, not with socket, since it is tricky to make one, but it is the exact same behavior.
Another solution is Referencing a value with a ref.
The idea is to create a ref and store there the value of current
each time it is updated:
const currentRef = useRef(null);
//...
useEffect(() => {
currentRef.current = current; // "current" property of the ref has nothing to do with "current" state; this is only a coincidence
}, [current]);
Now even with an empty dependency array, the other useEffect
can access the real-time value of currentRef.current
and use it instead of current
state.
This approach will not make the component unsubscribe/subscribe to "newConverstation" event each time current
is updated, as it is happening with the first one.
英文:
I understood why this was happening.
In most cases we use event handlers inside JSX as onClick
, onChange
... so each time the component rerenders those functions are recreated therefore we don't face this kind of issue.
Here this one is different, I had useEffect
with an empty dependency array so it will fire only once after the first render of the component so the event handler will always use the same referenced showCurrent
version when that useEffect
ran, and since at that time current
was equal to 0
then it will always be equal to 0
there.
I figured out that I need to make the event handler references to the newest created showCurrent
function each time current
is updated, so then access the newest/expected value of current
.
That being said, a strightforward solution is to include current
in the depenncy array of the effect, this will make it fire each time current
change value and doeq what we want it to:
useEffect(() => {
socket.on("newNotification", (payload) => {
//...
});
return () => {
socket.off("connect");
socket.off("newNotification");
};
}, [current]); // include "current"
Here is a sandbox example, not with socket, since it is tricky to make one, but it is the exact same behavior.
Another solution is Referencing a value with a ref.<br>
The idea is to to create a ref and store there the value of current
each time it is updated
const currentRef = useRef(null);
//...
useEffect(() => {
currentRef.current = current // "current" property of the ref has nothing to do with "current" state this is only a coincidence
},[current])
Now even with an empty dependency array, the other useEffect
can access the real-time value of currentRef.current
and use it instead of current
state.
This approach will not make the component unsubscribe/subscribe to "newConverstation" event each current
is updated as it is happenning with in first one.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论