【问题标题】:React useState(prevState => ...) not working when a listener callback updates state当侦听器回调更新状态时,React useState(prevState => ...) 不起作用
【发布时间】:2021-02-26 06:22:52
【问题描述】:

简介

我的屏幕上有一个进度时间栏,它在 setInterval 内的 useEffect 中高级。当我的 DB 侦听器被触发并执行其回调时,问题就出现了,代码会更新其他状态。

代码

const [isRoomFull, setIsRoomFull] = useState(false);
const [timerProgress, setTimerProgress] = useState(0);

 ...
const startGame = () => {
   ...
   advanceTimer();
}

const listenRoomChanges = (roomDoc) => {
   const { full } = roomDoc.data();

   setIsRoomFull(full);
};

const advanceTimer = () => {
  setTimerProgress(timerProgress + 1 / GAME_DURATION);
};

useEffect(() => {
    if (roomId) {
      const { firebase } = props;

      roomListener.current = firebase
        .getDatabase()
        .collection("rooms")
        .doc(roomId)
        .onSnapshot(listenRoomChanges); // <- DB listener callback
    }

    return () => {
      detachListener();
    };
  }, [roomId]);

useEffect(() => {
  if (timerProgress > 0 && timerProgress < 1) {
    // Advance the timer every second
    var timerInterval = setInterval(() => {
      advanceTimer();
    }, 1000);
  }

  return () => {
    clearInterval(timerInterval);
  };
}, [timerProgress]);

问题

问题是每次执行监听回调时timerProgress的值都会被重置。

例如,如果我在侦听器回调中执行 console.log(timerProgress)

const listenRoomChanges = (roomDoc) => {
   const { full } = roomDoc.data();
   console.log(timerProgress); // <----
   setIsRoomFull(full);
};

例如,当实际值为 10 秒时,它会显示 0。

我目前的解决方法

如果我在更新计时器进度时这样做

  const advanceTimer = () => {
    setTimerProgress(
      (prevTimerProgress) => prevTimerProgress + 1 / GAME_DURATION
    );
  };

相反,当 DB 侦听器回调更新其他状态时,timerProgress 不会“重置”为 0...

但由于某种原因,这不能 100% 正常工作(当 DB 侦听器回调更新其他状态时,timerProgress 提前 nStateUpdatesInTheDbListenerCallback 倍快,我不知道原因,但这正在发生)。

有人知道如何解决这个问题吗?有什么特殊的钩子吗?为什么会这样?

我认为我可以使用 useRef() 但这不会触发重新渲染...

谢谢。

【问题讨论】:

    标签: javascript reactjs react-native


    【解决方案1】:

    删除 return () =&gt; { clearInterval(time interval);};

    来自

    useEffect(() => {
      if (timerProgress > 0 && timerProgress < 1) {
        // Advance the timer every second
        var timerInterval = setInterval(() => {
          advanceTimer();
        }, 1000);
      }
    
      return () => {
        clearInterval(timerInterval);
      };
    }, [timerProgress]);
    

    让我知道,

    【讨论】:

    • 没用。当 timerProgress 更改时,timeInterval 会一次又一次地重新创建。这就是我清除它的原因。如果我这样做,时间间隔就会有问题,会增加和减少。
    • 好的,只需从useEffect(() =&gt; { if (timerProgress &gt; 0 &amp;&amp; timerProgress &lt; 1) { // Advance the timer every second var timerInterval = setInterval(() =&gt; { advanceTimer(); }, 1000); } return () =&gt; { clearInterval(timerInterval); }; }, []);中删除 timerProgress
    • 你想给一个空数组作为 useEffect 的第二个参数,这样函数在初始渲染后只运行一次。由于闭包的工作方式,这将使计数器变量始终引用初始值。您可以使用 setTimerProgress 的函数版本来始终获取正确的值。
    • useEffect(() =&gt; { const interval = setInterval(() =&gt; { setTimerProgress((prevTimerProgress) =&gt; prevTimerProgress + 1 / GAME_DURATION) }, 1000); return () =&gt; { clearInterval(interval); }; }, []);
    猜你喜欢
    • 2023-01-18
    • 1970-01-01
    • 2021-04-26
    • 2019-06-23
    • 2021-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-17
    相关资源
    最近更新 更多