skip to Main Content

Hello i just started developing mobile apps with react native and expo. Im making a simple timer app which is supposed to count down to 0 and you can pause, resume and stop the timer. I am currently stuck on a problem though and am unable to figure out what the problem is.

Here is a short video of the problem, the timer counts down like its supposed to but the resumeTimer() function doesnt seem to update the state correctly and still asumes it has the "resume" state even though it has already been switched to something else. Same thing with the secondsLeft state which is reset each time i set it to 0.

State Problem

Here is the code of the component:

// Imports //
import {
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from "react-native";
import { useState } from "react";
import Ionicons from "react-native-vector-icons/Ionicons";
import { AnimatedCircularProgress } from "react-native-circular-progress";
import TimeConverter from "../modules/TimeConverter";
import DateTimePicker from "@react-native-community/datetimepicker";

export default function Timer() {
  // State //
  const startingSeconds = 22;
  const [secondsLeft, setSecondsLeft] = useState(startingSeconds);
  const [state, setState] = useState("pause");

  // Functions //
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  const resumeTimer = async (secLeft) => {
    if (state == "resume" && secLeft > 0) {
      await sleep(1000);
      if (state == "resume") {
        setSecondsLeft(secLeft - 1);
        resumeTimer(secLeft - 1);
      }
    }
  };

  const pauseTimer = () => {
    setState("pause");
  };

  const stopTimer = () => {
    setState("stop");
    setSecondsLeft(0);
  };

  return (
    <View style={styles.container}>
      <View style={styles.topBar}>
        <Text style={styles.topBarTitle}>State: {state}</Text>
      </View>
      <View style={styles.timerContainer}>
        <AnimatedCircularProgress
          style={{ position: "absolute" }}
          size={300}
          width={5}
          fill={(secondsLeft / startingSeconds) * 100}
          tintColor="red"
          onAnimationComplete={() => console.log("onAnimationComplete")}
          backgroundColor="white"
        />
        <TouchableOpacity disabled={state == "resume" ? true : false}>
          <View style={styles.timerCircle}>
            <Text style={styles.timerText}>
              {TimeConverter.secondsToTimerString(secondsLeft)}
            </Text>
          </View>
        </TouchableOpacity>
      </View>
      <View style={styles.bottomBar}>
        <TouchableOpacity>
          <Ionicons
            style={styles.bottomBarItem}
            name={"pause-outline"}
            size={60}
            color={"white"}
            onPress={() => {
              pauseTimer();
            }}
          />
        </TouchableOpacity>
        <TouchableOpacity>
          <Ionicons
            style={styles.bottomBarItem}
            name={"play-outline"}
            size={60}
            color={"white"}
            onPress={() => {
              setState("resume");
              resumeTimer(secondsLeft);
            }}
          />
        </TouchableOpacity>
        <TouchableOpacity>
          <Ionicons
            style={styles.bottomBarItem}
            name={"stop-outline"}
            size={60}
            color={"white"}
            onPress={() => {
              stopTimer();
            }}
          />
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "space-between",
    backgroundColor: "#423D6A",
  },
  topBar: {
    flex: 0.13,
    backgroundColor: "#423D6A",
    alignItems: "center",
    justifyContent: "flex-end",
    borderColor: "white",
    borderBottomWidth: 1,
    borderBottomLeftRadius: 25,
    borderBottomRightRadius: 25,
  },
  topBarTitle: {
    fontSize: 30,
    color: "white",
    marginBottom: 10,
  },
  bottomBar: {
    flex: 0.2,
    backgroundColor: "#423D6A",
    flexDirection: "row",
    borderTopLeftRadius: 25,
    borderTopRightRadius: 25,
    justifyContent: "space-evenly",
    alignItems: "center",
    borderColor: "white",
    borderTopWidth: 1,
  },
  bottomBarItem: {
    marginBottom: "5%",
  },
  timerContainer: {
    flex: 0.67,
    marginTop: "10%",
    justifyContent: "center",
    alignItems: "center",
  },
  timerCircle: {
    width: 250,
    height: 250,
    borderRadius: 250 / 2,
    backgroundColor: "pink",
    justifyContent: "center",
    alignItems: "center",
  },
  timerText: {
    fontSize: 40,
  },
});

I dont even really know where to start debugging because i dont understand the problem. Im hoping to get an explanation on why it doesnt behave as I expected it to.

2

Answers


  1. Update this line

    const [secondsLeft, setSecondsLeft] = useState(22);
    
    Login or Signup to reply.
  2. try to update the stop method like this:

    const stopTimer = () => {
        setState("stop");
        setSecondsLeft(secondsLeft); // Update the state with the current value of secondsLeft
      };
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search