“从事件处理程序函数调用的状态不包含预期的值”

huangapple go评论72阅读模式
英文:

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 &quot;react&quot;;

export const DashboardNavbar = (props) =&gt; { 
  const socket = useContext(WebsocketContext);
  const [current, setCurrent] = useState(0);


  console.log(&quot;dashboard is rerendering...&quot;)

  const showCurrent = () =&gt; {
    console.log(&quot;showCurrent is running and current = &quot;, current);
  };

  const incrementCurent = () =&gt; {
    setCurrent((prev) =&gt; prev + 1);
  };

  useEffect(() =&gt; {
    socket.on(&quot;newNotification&quot;, (payload) =&gt; {
      // ...
      showCurrent();
    });
    return () =&gt; {
      // Unregistering Events...
      socket.off(&quot;connect&quot;);
      socket.off(&quot;newNotification&quot;);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    &lt;&gt;
      &lt;button
        onClick={() =&gt; {
          showCurrent();
        }}
      &gt;
        show{&quot; &quot;}
      &lt;/button&gt;

      &lt;button
        onClick={() =&gt; {
          incrementCurent();
        }}
      &gt;
        increment
      &lt;/button&gt;
    &lt;/&gt;
  );
}  
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(() =&gt; {
socket.on(&quot;newNotification&quot;, (payload) =&gt; {
//...
});
return () =&gt; {
socket.off(&quot;connect&quot;);
socket.off(&quot;newNotification&quot;);
};
}, [current]); // include &quot;current&quot;

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(() =&gt; {
  currentRef.current = current // &quot;current&quot; property of the ref has nothing to do with &quot;current&quot; 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.

huangapple
  • 本文由 发表于 2023年5月17日 06:27:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76267453.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定