简体   繁体   中英

Problem updating state when it is being used in an interval

I am building a React Native app and am trying to create a timer. The timer runs normally in the foreground using setInterval and updating a bit of state that holds the amount of seconds and decreases it every one second. When the app is backgrounded the date is captured and stored. When the active is made active again I am trying to calculate the difference between the two dates and then updating the state to reflect the difference so it appears to the user that the timer is running in the background.

I am running into an issue where the state is not updated when the timer is running.

const home = () => {

  const WORK_SECONDS = 1500; //1500
  const SHORT_REST_SECONDS = 300; //300
  const LONG_REST_SECONDS = 900; //900
  const ACTIVE_COLOR = '#009688';
  const PAUSED_COLOR = '#00695C';
  const ADD_TIME = 60;
  const NOTIFICATION_TITLE = "Time's up!";
  const NOTIFICATION_BODY = "Finished!";

  const [timer, setTimer] = useState({seconds: WORK_SECONDS, timeCap: WORK_SECONDS });
  const [isActive, setIsActive] = useState(false);
  const [timerColor, setTimerColor] = useState(ACTIVE_COLOR);
  const [sessionCycle, setSessionCycle] = useState([]);
  let time = 0;

  const toggle = () => {
    if (sessionCycle.length == 8) setSessionCycle([]);
    setIsActive(!isActive);
    setTimerColor((timerColor == ACTIVE_COLOR && timer.seconds != timer.timeCap) ? PAUSED_COLOR : ACTIVE_COLOR);
  }

  const reset = () => {
    setTimer({...timer, seconds: timer.timeCap});
    setIsActive(false);
  }

    useEffect(() => {
        AppState.addEventListener('change', handleChange);  
        return () => {
          AppState.removeEventListener('change', handleChange);  
        }
      }, []);

      const handleChange = (newState) => {
        if (newState === "active") {
          let resumeTime = moment(new Date());
          let exitTime = moment(time);
          let duration = moment.duration(resumeTime.diff(exitTime, 'seconds'));
          let diff = duration.asSeconds() * 1000;
          console.log("Attempting to substract - " + diff);
          console.log(timer.seconds + " from appstate");
          setTimer({...timer, seconds: timer.seconds - diff})
          console.log(timer.seconds + " after subtraction");
        }
        else if (newState == "background") {
          time = new Date();
        }
      }

      useEffect(() => {
        let interval = null;
        if (isActive) {
          if (timer.seconds == 0) {
            //sendNotification(); 
            // TODO: Add +1 to pomo total after finishing work session
            updateSessionCycle("forward");  
            clearInterval(interval);
            toggle();
          }
          interval = setInterval(() => {
            console.log(timer.seconds);
            setTimer({...timer, seconds: timer.seconds - 1});
          }, 1000);
        } else if (!isActive && timer.seconds !== 0) {
          clearInterval(interval);
        } 
        return () => clearInterval(interval);
      }, [isActive, timer.seconds]);

      const addTime = () => {
        if (timer.seconds > timer.timeCap - 60) {
          setTimer({...timer, seconds: timer.timeCap})
        } else {
          setTimer({...timer, seconds: timer.seconds + ADD_TIME});
        }
      }

    return (
        <View style={styles.chart}>
            <ProgressCircle
              percent={ (timer.seconds / timer.timeCap) * 100 }
              radius={Math.ceil(Dimensions.get('window').height / 6)}
              borderWidth={Math.ceil(Dimensions.get('window').height / 30)}
              color={timerColor}
              shadowColor="#212121"
              bgColor="#303030"
            >
              <Text style={{ color: '#FFF', fontSize: 50 }}>{moment("2015-01-01").startOf('day').seconds(timer.seconds).format('m:ss')}</Text>
              <Text style={{ color: '#9E9E9E', fontSize: 20 }}>{(timer.timeCap == WORK_SECONDS) ? "Work Session" : "Break"}</Text>
            </ProgressCircle>
        </View>
    );
}

I suspect your problem is you could be using stale state in your call to setTimer . In your useEffect , you are creating a setInterval function that references timer in your function closure. Each time that setInterval function is called, you'll be getting the same timer variable, so the call to:

setTimer({...timer, seconds: timer.seconds - 1});

...is going to continuously set seconds to 1 second less than the value of timer.seconds when you created the interval.

Luckily there's an easy workaround:

setTimer(timer => ({...timer, seconds: timer.seconds - 1}));

This will fetch the latest timer state each time before performing the update.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM