React useState(prevState = > …) не работает, когда обратный вызов слушателя обновляет состояние

#javascript #reactjs #react-native

#javascript #reactjs #реагировать — родной

Вопрос:

Введение

У меня на экране есть индикатор времени выполнения, который расширяется в useEffect, внутри setInterval. Проблема возникает, когда запускается мой прослушиватель БД и выполняет его обратный вызов, где код обновляет другие состояния.

Код

 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 amp;amp; 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);
};
  

он покажет мне 0, например, когда реальное значение равно 10 секундам.

Мой текущий обходной путь

Если при обновлении хода выполнения таймера я делаю

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

вместо этого timerProgress не будет «сбрасываться» на 0, когда обратный вызов прослушивателя БД обновляет другие состояния…

Но по какой-то причине это работает не на 100% хорошо (когда обратный вызов прослушивателя БД обновляет другие состояния, timerProgress ускоряет nStateUpdatesInTheDbListenerCallback в разы быстрее, я не знаю причину, но это происходит).

Кто-нибудь знает, как решить эту проблему? Какой-либо специальный хук для этого? Почему это происходит?

Я думаю, что я могу использовать useRef(), но это не приведет к повторному рендерингу…

Спасибо.

Ответ №1:

Удалить return () => { clearInterval(time interval);};

От

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

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

И дайте мне знать,

Комментарии:

1. Не сработало. timeInterval воссоздается снова и снова при изменении timerProgress. Вот почему я его очищаю. Если я это сделаю, временной интервал будет глючить, увеличиваясь и уменьшаясь.

2. Хорошо, просто удалите timerProgress из useEffect(() => { if (timerProgress > 0 amp;amp; timerProgress < 1) { // Advance the timer every second var timerInterval = setInterval(() => { advanceTimer(); }, 1000); } return () => { clearInterval(timerInterval); }; }, []);

3. Вы хотите предоставить пустой массив в качестве второго аргумента useEffect, чтобы функция запускалась только один раз после первоначального рендеринга. Из-за того, как работают замыкания, это заставит переменную counter всегда ссылаться на начальное значение. Вместо этого вы можете использовать функциональную версию setTimerProgress, чтобы всегда получать правильное значение.

4. useEffect(() => { const interval = setInterval(() => { setTimerProgress((prevTimerProgress) => prevTimerProgress 1 / GAME_DURATION) }, 1000); return () => { clearInterval(interval); }; }, []);