React interval timer is not exiting when countdown gets to zero - javascript

probably a simple answer to my issue, but it is one that I cannot figure out. My countdown keeps on going past zero and into the negatives, I would like it to end when it hits zero. What am I doing wrong?
Thanks!
const [staticCountdown, setStaticCountdown] = useState(15);
const [countdown, setCountdown] = useState(15);
const setCount = (count) => {
if (!props.timerActive) {
setStaticCountdown(count);
setCountdown(count);
}
};
useEffect(() => {
let interval = null;
if (props.timerActive) {
interval = setInterval(() => {
setCountdown(countdown => countdown - 1);
}, 1000);
} else if (!props.timerActive && countdown === 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [props.timerActive, countdown]);

You should change
else if (!props.timerActive && countdown === 0)
into
(if (!props.timerActive || countdown === 0)
here is an example based on you code:
const {
useEffect,
useState
} = React;
function App(){
const [timerActive, setTimerActive] = useState(true);
const handleTimerActive = () => {
setTimerActive((prev) => !prev);
};
return (
<div className="App">
<Timer timerActive={timerActive} />
<button onClick={handleTimerActive}>
{timerActive ? "Stop" : "Run"}
</button>
</div>
);
}
function Timer(props) {
const [countdown, setCountdown] = useState(15);
useEffect(() => {
let interval = null;
if (props.timerActive) {
interval = setInterval(() => {
setCountdown((countdown) => countdown - 1);
}, 1000);
}
if (!props.timerActive || countdown === 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [props.timerActive, countdown]);
const handleReset = () => {
setCountdown(15);
};
return (
<div>
<h1>{countdown}</h1>
<button onClick={handleReset}>Reset</button>
</div>
);
}
ReactDOM.render( <App/> ,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

How to replace react component with another when event triggered in that component

So, i (Total react beginner) have this React component.
import './cryptography.css';
import {useRef} from 'react';
import useInterval from 'react-useinterval';
import { useState } from 'react';
const DisplayTimer = () => {
const [time, setTime] = useState(0);
useInterval(() => {
setTime(time + 1);
}
, 1000);
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
return (
<div className="timer">
<h3>Timer</h3>
<p>{hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}</p>
</div>
)
}
const CryptoOne = () => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "c6e24b99a8054ab24dd0323530b80819") {
alert("Correct!");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-one">
<h3>Level One</h3>
<p>Convert this plaintext into an MD5 hash - "RollingStonesGatherNoMoss"</p>
<input ref={inputRef} type="text" id="one-answer" name="one-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const CryptoTwo = () => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "IaMaHackerManHaHa") {
alert("Correct!");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-two">
<h3>Level Two</h3>
<p>Decode Caesar cipher into plaintext - "FxJxExzhboJxkExEx"</p>
<input ref={inputRef} type="text" id="two-answer" name="two-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const CryptoThree = () => {
const inputRef = useRef();
function verifyAnswer() {
let input = inputRef.current.value;
let answer = "SHA256".toLowerCase();
if (input === answer) {
alert("Correct!, completed the exercise");
}
else {
alert("Wrong!");
}
}
return (
<div className="crypto-three">
<h3>Level Three</h3>
<p>Identify the type of the hash (Type only the hash type, with no spaces) - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"</p>
<input ref={inputRef} type="text" id="three-answer" name="three-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
)
}
const Cryptography = () => {
const [active, setActive] = useState("crypto-one");
return (
<div className="cryptography">
<h1>Cryptography</h1>
<DisplayTimer />
<div className="crypto-container">
<CryptoOne />
</div>
</div>
)
}
export { Cryptography };
Here, i have this cryptography function that will display a question and if it is correct, that question will be replaced with another.
I have 3 seperate functions CryptoOne, CryptoTwo and CryptoThree which will be displayed in function Cryptography. So that means if the answer for the question for CryptoOne is correct, then it will be replaced with CryptoTwo and so on. So my questions is HOW DO I DO IT?
Thanks in advance.
ANSWER TO YOUR LAST COMMENT TO HANDLE TIMER
Instead of using react-useinterval you could create a useTimer() hook of your own
import { useEffect, useRef, useState } from "react";
const useTimer = () => {
const [time, setTime] = useState(0);
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
let interval = useRef();
useEffect(() => {
interval.current = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
return () => {
clearInterval(interval.current);
};
}, [setTime]);
const stopTimer = () => {
clearInterval(interval.current);
};
return { hours, minutes, seconds, stopTimer };
};
export default useTimer;
You can maintain a activeIndex state in Cryptography component. And have a changeSlide method which increments activeSlideIndex by 1 everytime the answer is correct. As shown below
TIMER RELATED COMMENT
Use that hook in Cryptography component. Pass hours, minutes & seconds as props in DisplayTimer component. And pass stopTimer function to CryptoThree component & use it when final answer is complete
const Cryptography = () => {
const [active, setActive] = useState("crypto-one");
const [activeIndex, setActiveIndex] = useState(1);
const { hours, minutes, seconds, stopTimer } = useTimer();
const incrementIndex = () => {
setActiveIndex(prevIndex => prevIndex + 1);
};
return (
<div className="cryptography">
<h1>Cryptography</h1>
<DisplayTimer hours={hours} minutes={minutes} seconds={seconds} />
<div className="crypto-container">
{activeSlideIndex === 1 && <CryptoOne changeIndex={incrementIndex} />}
{activeSlideIndex === 2 && <CryptoTwo changeIndex={incrementIndex} />}
{activeSlideIndex === 3 && <CryptoThree stopTimer={stopTimer} />}
</div>
</div>
);
};
const DisplayTimer = ({ hours, minutes, seconds }) => {
return (
<div className="timer">
<h3>Timer</h3>
<p>
{hours.toString().padStart(2, "0")}:
{minutes.toString().padStart(2, "0")}:
{seconds.toString().padStart(2, "0")}
</p>
</div>
);
};
You can pass this changeSlide() method as a prop in every component.
And call it whenever the answer is correct
const CryptoOne = ({ changeIndex }) => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "c6e24b99a8054ab24dd0323530b80819") {
alert("Correct!");
changeIndex();
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-one">
<h3>Level One</h3>
<p>
Convert this plaintext into an MD5 hash - "RollingStonesGatherNoMoss"
</p>
<input ref={inputRef} type="text" id="one-answer" name="one-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};
const CryptoTwo = ({ changeIndex }) => {
const inputRef = useRef();
function verifyAnswer() {
if (inputRef.current.value === "IaMaHackerManHaHa") {
alert("Correct!");
changeIndex();
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-two">
<h3>Level Two</h3>
<p>Decode Caesar cipher into plaintext - "FxJxExzhboJxkExEx"</p>
<input ref={inputRef} type="text" id="two-answer" name="two-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};
const CryptoThree = ({stopTimer) => {
const inputRef = useRef();
function verifyAnswer() {
let input = inputRef.current.value;
let answer = "SHA256".toLowerCase();
if (input === answer) {
alert("Correct!, completed the exercise");
stopTimer(); // Use stopTimer here when exercise is complete
} else {
alert("Wrong!");
}
}
return (
<div className="crypto-three">
<h3>Level Three</h3>
<p>
Identify the type of the hash (Type only the hash type, with no spaces)
- "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
</p>
<input ref={inputRef} type="text" id="three-answer" name="three-answer" />
<button onClick={verifyAnswer}>Submit</button>
</div>
);
};

How to change image every second in map react

Im trying to change my image every second in react inside of a map. However this doesnt seem to be working. Here is my code:
function ImageGrid() {
const {shell, gridImage} = styles
const squishAnimals = [
'/SquishAnimalDemo(1).png',
'/SquishAnimalDemo(2).png',
'/SquishAnimalDemo(3).png',
'/SquishAnimalDemo(4).png',
'/SquishAnimalDemo(5).png',
'/SquishAnimalDemo(6).png',
'/SquishAnimalDemo(7).png',
'/SquishAnimalDemo(8).png',
'/SquishAnimalDemo(9).png'
];
const [images, setImages] = useState(squishAnimals);
const [currentImage, setCurrentImage] = useState(0);
function changeBackgroundImage() {
let newCurrentImg = 0;
const noOfImages = images.length;
if (currentImage !== noOfImages - 1) {
newCurrentImg += currentImage;
}
setCurrentImage(newCurrentImg);
}
useEffect(() => {
const interval = setInterval(() => changeBackgroundImage(), 1000);
if (interval) {
clearInterval(interval);
}
});
return (
<div className={shell}>
{squishAnimals.map((image, index) => {
return (
index === 4 ?
<img className={gridImage} src={images[currentImage]} alt={`${index}-${image}`}/> :
<img className={gridImage} src={image} alt={`${index}-${image}`}/>
)
})}
</div>
);
}
Any help as to why it may not be working would be great. Thanks :)
I think it's because you immediately cancel the interval in useEffect. I believe you want to return a cleanup function to clear the interval id.
function ImageGrid() {
const {shell, gridImage} = styles
const squishAnimals = [
'/SquishAnimalDemo(1).png',
'/SquishAnimalDemo(2).png',
'/SquishAnimalDemo(3).png',
'/SquishAnimalDemo(4).png',
'/SquishAnimalDemo(5).png',
'/SquishAnimalDemo(6).png',
'/SquishAnimalDemo(7).png',
'/SquishAnimalDemo(8).png',
'/SquishAnimalDemo(9).png'
];
const [images, setImages] = useState(squishAnimals);
const [currentImage, setCurrentImage] = useState(0);
function changeBackgroundImage() {
let newCurrentImg = 0;
const noOfImages = images.length;
if (currentImage !== noOfImages - 1) {
newCurrentImg += currentImage;
}
setCurrentImage(newCurrentImg);
}
useEffect(() => {
const interval = setInterval(() => changeBackgroundImage(), 1000);
return () => {
if (interval) {
clearInterval(interval);
}
}
});
return (
<div className={shell}>
{squishAnimals.map((image, index) => {
return (
index === 4 ?
<img className={gridImage} src={images[currentImage]} alt={`${index}-${image}`} key={images[currentImage]}/> :
<img className={gridImage} src={image} alt={`${index}-${image}`} key = {`${index}-${image}`}/>
)
})}
</div>
);
}

How to seperate the two functions in ReactJS?

Introduction
I am building the countdown timer, which I had built but now I want to scale it up. So I am building two countdowns on the same page with each of them having separate start, stop buttons, and common reset button.
What do I have?
I have implemented but they are not working independently. For example, when I click on the stop of the upper clock, it also stops the lower clock.
What do I want?
I wanted to have separate functions for both of them but on the same page.
Code
Most of the things are repeated in the code.
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";
const STATUS = {
STARTED: "Started",
STOPPED: "Stopped"
};
const STATUSp1 = {
STARTED: "Started",
STOPPED: "Stopped"
};
export default function InputCountDown() {
const [time, setTime] = useState(0);
const [timep1, setTimep1] = useState(0);
const [inc, setInc] = useState(0);
const handleOnChange = (e) => {
//console.log(e.target.value);
setTime(e.target.value);
setTimep1(e.target.value);
};
const handleOnChangeIncrement = (e) => {
console.log(e.target.value);
setInc(e.target.value);
};
const [secondsRemaining, setSecondsRemaining] = useState(time * 60);
const [secondsRemainingp1, setSecondsRemainingp1] = useState(timep1 * 60);
//console.log(time);
const [status, setStatus] = useState(STATUS.STOPPED);
const [statusp1, setStatusp1] = useState(STATUSp1.STOPPED);
const secondsToDisplay = secondsRemaining % 60;
const minutesRemaining = (secondsRemaining - secondsToDisplay) / 60;
const minutesToDisplay = minutesRemaining % 60;
const hoursToDisplay = (minutesRemaining - minutesToDisplay) / 60;
const secondsToDisplayp1 = secondsRemainingp1 % 60;
const minutesRemainingp1 = (secondsRemainingp1 - secondsToDisplayp1) / 60;
const minutesToDisplayp1 = minutesRemainingp1 % 60;
const hoursToDisplayp1 = (minutesRemainingp1 - minutesToDisplayp1) / 60;
const handleStart = () => {
setStatus(STATUS.STARTED);
setSecondsRemaining(time * 60);
};
const handleStartp1 = () => {
setStatusp1(STATUSp1.STARTED);
setSecondsRemainingp1(timep1 * 60);
};
const handleStop = () => {
setStatus(STATUS.STOPPED);
};
const handleStopp1 = () => {
setStatusp1(STATUSp1.STOPPED);
};
const handleReset = () => {
setStatus(STATUS.STOPPED);
setSecondsRemaining(time * 60);
setSecondsRemainingp1(timep1 * 60);
};
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1);
} else {
setStatus(STATUS.STOPPED);
}
if (secondsRemainingp1 > 0) {
setSecondsRemainingp1(secondsRemainingp1 - 1);
} else {
setStatusp1(STATUSp1.STOPPED);
}
},
status === STATUS.STARTED ? 1000 : null,
statusp1 === STATUSp1.STARTED ? 1000 : null
// passing null stops the interval
);
return (
<>
<div className="App">
<h1>Countdown Using Input</h1>
<div style={{ padding: "12px" }}>
<label htmlFor="time"> Enter time in minutes </label>
<input
type="text"
id="time"
name="time"
value={time}
onChange={(e) => handleOnChange(e)}
/>
</div>
<div style={{ padding: "12px" }}>
<label htmlFor="inc"> Enter increment </label>
<input
type="number"
id="inc"
name="inc"
value={inc}
onChange={(e) => handleOnChangeIncrement(e)}
/>
</div>
<button onClick={handleStart} type="button">
Start
</button>
<button onClick={handleStop} type="button">
Stop
</button>
<button onClick={handleReset} type="button">
Reset
</button>
<div>
<button
onClick={() =>
setSecondsRemaining(secondsRemaining + parseInt(inc, 10))
}
>
Increment {inc} sec
</button>
</div>
<div style={{ padding: 20, fontSize: "40px" }}>
{twoDigits(hoursToDisplay)}:{twoDigits(minutesToDisplay)}:
{twoDigits(secondsToDisplay)}
</div>
<div>Status: {status}</div>
<div style={{ padding: 20, fontSize: "40px" }}>
{twoDigits(hoursToDisplayp1)}:{twoDigits(minutesToDisplayp1)}:
{twoDigits(secondsToDisplayp1)}
</div>
<button onClick={handleStartp1} type="button">
Start_p1
</button>
<button onClick={handleStopp1} type="button">
Stop_p1
</button>
<button onClick={handleReset} type="button">
Reset_p1
</button>
</div>
</>
);
}
// source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
// https://stackoverflow.com/a/2998874/1673761
const twoDigits = (num) => String(num).padStart(2, "0");
You can also find the code in the codesandbox in the inputCountDown.js file .
You can ignore increment button
Please help me !!!
You are using the same interval for two different counters, which causes them to sync up on the same time. Nevertheless, this code is handling too many responsibilities at once and contains unnecessary duplications that is polluting the readability of the code.
Instead of duplicating the same code with different variables names, try extracting it into its own component, and call that component twice. This way, more code isolation is ensured and is definitely less error prone, while improving readability.
Seperate the useInterval logic for each of the timer
useInterval(
() => {
if (secondsRemaining > 0) {
setSecondsRemaining(secondsRemaining - 1);
} else {
setStatus(STATUS.STOPPED);
}
},
status === STATUS.STARTED ? 1000 : null
);
useInterval(
() => {
if (secondsRemainingp1 > 0) {
setSecondsRemainingp1(secondsRemainingp1 - 1);
} else {
setStatusp1(STATUSp1.STOPPED);
}
},
statusp1 === STATUSp1.STARTED ? 1000 : null
// passing null stops the interval
);

React Countdown Circle Timer Resetting When Switching Tabs

In my project, I'm using the React Countdown Clock Timer NPM package. I have two of these timers show up at once, a minutes timer and seconds timer. Everything works fine except when I switch tabs my minutes keeps the correct amount of minutes, while my seconds timer resets. I want the seconds timer to still keep the correct amount of settings even when I switch tabs.
This is my current file for the timer where the iies
import React, { useState, useContext } from "react";
import { CountdownCircleTimer } from "react-countdown-circle-timer";
import { AuthContext } from "../../context/Auth";
import { ChillContext } from "../../context/ChillContext";
import { db } from "../../services/firebase";
import firebase from "firebase/app";
import chime from "../../sounds/chime.mp3";
import {
CountdownTimerWrapper,
CountdownTimerMain,
TimersWrapper,
RestartButton,
StartStopButton,
ButtonsWrapper,
TimerCheck,
TimerHeading,
TimerControl,
TimerText,
} from "./CountdownTimerElements";
import Toggle from "../Toggle/Toggle";
import { Howl } from "howler";
const minuteSeconds = 60;
const hourSeconds = 3600;
const renderTime = (dimension, time) => {
return (
<div className="time-wrapper">
<div className="time">{time}</div>
<div>{dimension}</div>
</div>
);
};
const getTimeSeconds = (time) => (minuteSeconds - time) | 0;
const getTimeMinutes = (time) => ((time % hourSeconds) / minuteSeconds) | 0;
const CountdownTimer = () => {
const stratTime = Date.now() / 1000; // use UNIX timestamp in seconds
const workEndTime = stratTime + 25 * 60; // use UNIX timestamp in seconds
const chillEndTime = stratTime + 5 * 60;
const [minutesKey, setMinutesKey] = useState(0);
const [secondsKey, setSecondsKey] = useState(10);
const [playing, setPlaying] = useState(false);
const [timerButtonDesc, setTimerButtonDesc] = useState("Start");
const { currentUser } = useContext(AuthContext);
const { chill, setChill } = useContext(ChillContext);
const chillRemainingTime = chillEndTime - stratTime;
const workRemainingTime = workEndTime - stratTime;
const timerProps = {
isPlaying: playing,
size: 100,
strokeWidth: 6,
};
const sound = new Howl({
src: [chime],
});
return (
<CountdownTimerWrapper>
<CountdownTimerMain>
<TimerHeading>Pomodoro Timer</TimerHeading>
{chill ? (
<TimersWrapper>
<CountdownCircleTimer
{...timerProps}
colors={[["#9294e3"]]}
key={minutesKey}
duration={hourSeconds}
initialRemainingTime={chillRemainingTime % hourSeconds}
onComplete={(totalElapsedTime) => {
return [chillRemainingTime - totalElapsedTime > minuteSeconds];
}}
>
{({ elapsedTime }) =>
renderTime("minutes", getTimeMinutes(hourSeconds - elapsedTime))
}
</CountdownCircleTimer>
<TimerControl>
<Toggle
onChange={(event) => {
setChill(event.target.checked);
setSecondsKey((prevKey) => prevKey + 1);
setMinutesKey((prevKey) => prevKey + 1);
setTimerButtonDesc("Start");
setPlaying(false);
}}
/>
<TimerText>{chill ? "Chill" : "Work"} Timer </TimerText>
</TimerControl>
<CountdownCircleTimer
{...timerProps}
colors={[["#9294e3"]]}
key={secondsKey}
duration={minuteSeconds}
initialRemainingTime={chillRemainingTime % minuteSeconds}
onComplete={(totalElapsedTime) => {
console.log(totalElapsedTime - chillRemainingTime);
if (totalElapsedTime - chillRemainingTime === 0) {
return false;
}
return [chillRemainingTime - totalElapsedTime > 0];
}}
>
{({ elapsedTime }) =>
renderTime("seconds", getTimeSeconds(elapsedTime))
}
</CountdownCircleTimer>
</TimersWrapper>
) : (
<TimersWrapper>
<CountdownCircleTimer
{...timerProps}
colors={[["#d87463"]]}
key={minutesKey}
duration={hourSeconds}
initialRemainingTime={workRemainingTime % hourSeconds}
onComplete={(totalElapsedTime) => {
return [workRemainingTime - totalElapsedTime > minuteSeconds];
}}
>
{({ elapsedTime }) =>
renderTime("minutes", getTimeMinutes(hourSeconds - elapsedTime))
}
</CountdownCircleTimer>
<TimerControl>
<Toggle
onChange={(event) => {
setChill(event.target.checked);
setSecondsKey((prevKey) => prevKey + 1);
setMinutesKey((prevKey) => prevKey + 1);
setTimerButtonDesc("Start");
setPlaying(false);
}}
/>
<TimerText>{chill ? "Chill" : "Work"} Timer </TimerText>
</TimerControl>
<CountdownCircleTimer
{...timerProps}
colors={[["#d87463"]]}
key={secondsKey}
duration={minuteSeconds}
initialRemainingTime={workRemainingTime % minuteSeconds}
onComplete={(totalElapsedTime) => {
if (totalElapsedTime - workRemainingTime === 0) {
db.collection("emails")
.doc(currentUser.email)
.update({
pomodoros: firebase.firestore.FieldValue.increment(1),
});
sound.play();
}
return [workRemainingTime - totalElapsedTime > 0];
}}
>
{({ elapsedTime }) =>
renderTime("seconds", getTimeSeconds(elapsedTime))
}
</CountdownCircleTimer>
</TimersWrapper>
)}
<ButtonsWrapper>
<RestartButton
onClick={() => {
setSecondsKey((prevKey) => prevKey + 1);
setMinutesKey((prevKey) => prevKey + 1);
setPlaying(false);
setTimerButtonDesc("Start");
}}
>
Restart Timer
</RestartButton>
<TimerCheck />
<StartStopButton
onClick={() => {
if (timerButtonDesc === "Start") {
setTimerButtonDesc("Stop");
} else {
setTimerButtonDesc("Start");
}
setPlaying(!playing);
}}
>
{timerButtonDesc}
</StartStopButton>
</ButtonsWrapper>
</CountdownTimerMain>
</CountdownTimerWrapper>
);
};
export default CountdownTimer;
The issue you have is that you are setting "key" to minutesKey.
Everytime you change "key" the timer will reset. You want to pass a constant number in if reset is not a part of the folw. If reset is part of the flow, have a separate state for it.
<CountdownCircleTimer
{...timerProps}
colors={[["#d87463"]]}
key={0}
duration={minuteSeconds}
initialRemainingTime={workRemainingTime % minuteSeconds}
onComplete={(totalElapsedTime) => {
if (totalElapsedTime - workRemainingTime === 0) {
db.collection("emails")
.doc(currentUser.email)
.update({
pomodoros: firebase.firestore.FieldValue.increment(1),
});
sound.play();
}
return [workRemainingTime - totalElapsedTime > 0];
}}
>
{({ elapsedTime }) =>
renderTime("seconds", getTimeSeconds(elapsedTime))
}
</CountdownCircleTimer>

React functional component: clearInterval() does not clear my interval

I'm building a simple timer app. When the user clicks play the handlePlayPause function is called. I've created an isRunning boolean to check if the timer is already running. If it's not, the timer will start (this first part works), whilst if it is, the pauseTimer function is called. This last function switches isRunning back to false and should clear the interval. However, the interval does not get cleared. Can you see what i'm doing wrong?
Thank you for your help!
export default function App() {
const [sessionLength, setSessionLength] = useState(25)
const [breakLength, setBreakLength] = useState(5)
const [timeLeft, setTimeLeft] = useState(25 * 60 * 1000)
const [isRunning, setIsRunning] = useState(false)
let intervalId = null
let handlePlayPause = () => {
if (!isRunning) {
setTimeLeft(sessionLength * 60 * 1000)
playTimer()
} else if (isRunning) {
pauseTimer()
}
}
let playTimer = () => {
setIsRunning(true)
intervalId = setInterval(() => {
setTimeLeft(timeLeft => timeLeft - 1000)
parseToMinuteSeconds(timeLeft)
}, 1000)
}
let pauseTimer = () => {
setIsRunning(false)
clearInterval(intervalId)
}
let resetAll = () => {
setSessionLength(25)
setBreakLength(5)
}
let parseToMinuteSeconds = timeInMilliseconds => {
return //a string with the time in this format 00:00
}
return (
<div className="App">
<Background>
<UpperMetalBand />
<UpperPart />
<LowerPart />
<LowerMetalBand />
<Wrapper>
<Title>Pomodoro</Title>
<Subtitle>TIMER</Subtitle>
<PlayButton
id="start_stop"
onClick = {handlePlayPause}
>
<i className="fa fa-play" />
</PlayButton>
<Reload
onClick={resetAll}
id="reset"
>
<i className="fas fa-sync-alt" />
</Reload>
<Session
setSessionLength={setSessionLength}
sessionLength={sessionLength}
/>
<Break
setBreakLength={setBreakLength}
breakLength={breakLength}
/>
<span id="time-label">
<Timer id="time-left">00:00</Timer>
</span>
</Wrapper>
</Background>
</div>
)
}
I think the problem is with how you store the interval id. when using function components and we want to store "instance" variables we can use the useRef hook.
let intervalId = useRef(null)
let handlePlayPause = () => {
if (!isRunning) {
setTimeLeft(sessionLength * 60 * 1000)
playTimer()
} else if (isRunning) {
pauseTimer()
}
}
let playTimer = () => {
setIsRunning(true)
intervalId.current = setInterval(() => {
console.log('interval')
setTimeLeft(timeLeft => timeLeft - 1000)
parseToMinuteSeconds(timeLeft)
}, 1000)
}
let pauseTimer = () => {
setIsRunning(false)
clearInterval(intervalId.current)
}
There is a similar example to your use case on the react docs

Categories

Resources