Reset countdown timer in on button click in react - reactjs

i have this code basically a kind of quiz app. I want to do rest timer when user click on next or previous button. Need help for this. We can do that by creating only single component and create one of state as reset but in that case every second main page get re-render.
const CalcTimeLeft = () => {
const [time, setTime] = useState(300000);
useEffect(() => {
setTimeout(() => {
setTime(time - 1000);
}, 1000);
}, [time]);
const timeLeft = {
minutes: Math.floor(time / 60 / 1000) % 60,
seconds: Math.floor(time / 1000) % 60
};
return <div>{`${timeLeft.minutes}:${timeLeft.seconds}`}</div>;
};
export default function App() {
const [currPage, setCurrpage] = useState(1);
const question = [
];
const handleNext = () => {
setCurrpage(currPage + 1);
};
const handlePrev = () => {
setCurrpage(currPage - 1);
};
let item_per_page = 1;
let total_page = Math.floor(question.length / item_per_page);
const data = question.splice((currPage - 1) * item_per_page, item_per_page);
return (
<div className="App">
{data.map((data) => {
return <div>{data.question}</div>;
})}
<CalcTimeLeft />
{currPage !== total_page && <button onClick={handleNext}>Next</button>}
{currPage !== 1 && (
<button onClick={() => setCurrpage(handlePrev)}>Previous</button>
)}
</div>
);
}

You can bubble the reset event in the CalcTimeLeft component.
const CalcTimeLeft = () => {
const [time, setTime] = useState(300000);
const resetTime = () => {
setTime(300000);
};
useEffect(() => {
setTimeout(() => {
setTime(time - 1000);
}, 1000);
}, [time]);
return { resetTime };
};
export default function App() {
const [currPage, setCurrpage] = useState(1);
const question = [];
const { resetTime } = CalcTimeLeft();
const handleNext = () => {
resetTime;
setCurrpage(currPage + 1);
};
const handlePrev = () => {
resetTime;
setCurrpage(currPage - 1);
};
const timeLeft = {
minutes: Math.floor(time / 60 / 1000) % 60,
seconds: Math.floor(time / 1000) % 60,
};
let item_per_page = 1;
let total_page = Math.floor(question.length / item_per_page);
const data = question.splice((currPage - 1) * item_per_page, item_per_page);
return (
<div className="App">
{data.map((data) => {
return <div>{data.question}</div>;
})}
<div>{`${timeLeft.minutes}:${timeLeft.seconds}`}</div>
{currPage !== total_page && <button onClick={handleNext()}>Next</button>}
{currPage !== 1 && <button onClick={handlePrev()}>Previous</button>}
</div>
);
}

Related

Countdown react timer sets time to zero

The goal is to implement a timer with a start, pause/resume, and reset buttons and also with custom input for getting minutes and seconds.
As the requirement states, the timer should not change when tying the input field, the timer should change only when start button is pressed (it show set state only after start is pressed).
Code:
const START_DERATION = 10;
function Timer() {
const [min, setMin] = useState("00");
const [sec, setSec] = useState("00");
const [currentMinutes, setMinutes] = useState("00");
const [currentSeconds, setSeconds] = useState("00");
const [isStop, setIsStop] = useState(false);
const [duration, setDuration] = useState(START_DERATION);
const [isRunning, setIsRunning] = useState(false);
const formatTime = async () => {
setMinutes(min);
setSeconds(sec);
};
const startHandler = async () => {
setDuration(
parseInt(currentSeconds, 10) + 60 * parseInt(currentMinutes, 10)
);
setIsRunning(true);
};
const stopHandler = () => {
setIsStop(true);
setIsRunning(false);
};
const resetHandler = () => {
setMinutes("00");
setSeconds("00");
setIsRunning(false);
setIsStop(false);
setDuration(START_DERATION);
};
const resumeHandler = () => {
let newDuration =
parseInt(currentMinutes, 10) * 60 + parseInt(currentSeconds, 10);
setDuration(newDuration);
setIsRunning(true);
setIsStop(false);
};
const startAll = async () => {
await formatTime();
startHandler();
};
useEffect(() => {
if (isRunning === true) {
let timer = duration;
var minutes, seconds;
const interval = setInterval(function () {
if (--timer <= 0) {
resetHandler();
} else {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
setMinutes(minutes);
setSeconds(seconds);
}
}, 1000);
return () => clearInterval(interval);
}
}, [isRunning]);
useEffect(() => {
console.log(currentMinutes);
}, [currentMinutes]);
useEffect(() => {
console.log(currentSeconds);
}, [currentSeconds]);
return (
<div>
<span style={{ display: "flex" }}>
<input type="number" onChange={(e) => setMin(e.target.value + "")} />{" "}
<p>Minutes</p>
</span>
<span style={{ display: "flex" }}>
<input type="number" onChange={(e) => setSec(e.target.value + "")} />
<p>Seconds</p>
</span>
<button onClick={formatTime}>Format</button>
<button onClick={startAll}>Start</button>
<button
onClick={isStop ? resumeHandler : stopHandler}
disabled={!isRunning && !isStop}
>
Pause/Resume
</button>
<button onClick={resetHandler} disabled={!isRunning && !isStop}>
Reset
</button>
<p>
{currentMinutes}:{currentSeconds}
</p>
</div>
);
}
But in the above case, the timer is set to 0 when the start is pressed.
Codesandbox : https://codesandbox.io/s/lively-grass-6o50xx?file=/src/Timer.js

React click specific element in setInterval loop

I'm trying to click my element in setInterval loop, so it would be clicked every 10 second, but there's always error click is not a function or cannot read click null
I've tired with useRef and also did nothing.
here is my code:
useEffect(() => {
setInterval(function () {
const handleChangeState = () => {
console.log("Now");
document.getElementById("dice").click();
};
handleChangeState();
}, 10 * 1000);
}, []);
return (
<>
<Dice id="dice" rollingTime="3000" triggers={["click", "P"]} />
</>
);
};
It is often considered anti-pattern in React to query the DOM. You should instead use a React ref to gain access to the underlying DOMNode.
There are a couple ways to use a React ref to invoke a dice roll of the child component. FYI, rollingTime should probably be number type instead of a string if using in any setTimeout calls.
Forward the React ref and attach to the button element and invoke the click handler.
Example:
const Dice = forwardRef(({ id, rollingTime }, ref) => {
const timerRef = useRef();
const [value, setValue] = useState();
const [isRolling, setIsRolling] = useState();
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
const roll = () => {
if (!isRolling) {
setIsRolling(true);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setValue(Math.floor(Math.random() * 6) + 1);
setIsRolling(false);
}, rollingTime);
}
};
return (
<>
<h1>Dice</h1>
<h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
<button ref={ref} id={id} type="button" onClick={roll}>
Roll the dice
</button>
</>
);
});
...
export default function App() {
const diceRef = useRef();
useEffect(() => {
const handleChangeState = () => {
console.log("Clicking Dice");
diceRef.current?.click();
};
setInterval(() => {
handleChangeState();
}, 10 * 1000);
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}
Forward the React ref and invoke the button's callback function directly via the useImperativeHandle hook.
Example:
const Dice = forwardRef(({ id, rollingTime }, ref) => {
const timerRef = useRef();
const [value, setValue] = useState();
const [isRolling, setIsRolling] = useState();
useEffect(() => {
return () => clearTimeout(timerRef.current);
}, []);
const roll = () => {
if (!isRolling) {
setIsRolling(true);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setValue(Math.floor(Math.random() * 6) + 1);
setIsRolling(false);
}, rollingTime);
}
};
useImperativeHandle(ref, () => ({
roll
}));
return (
<>
<h1>Dice 2</h1>
<h2>Roll Value: {isRolling ? "Rolling..." : value}</h2>
<button id={id} type="button" onClick={roll}>
Roll the dice
</button>
</>
);
});
...
export default function App() {
const diceRef = useRef();
useEffect(() => {
const handleRollDice = () => {
console.log("Roll dice");
diceRef.current.roll();
};
setInterval(() => {
handleRollDice();
}, 10 * 1000);
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}
Using react-dice-roll
If you examine the react-dice-roll source code you'll see that the Dice component forwards a React ref and uses the useImperativeHandle hook to expose out a rollDice function.
Dice Source
const Dice = forwardRef((props: TProps, ref: React.MutableRefObject<TDiceRef>) => {
...
const handleDiceRoll = (value?: TValue) => {
let diceAudio: HTMLAudioElement;
if (sound) {
diceAudio = new Audio(sound);
diceAudio.play();
}
setRolling(true);
setTimeout(() => {
let rollValue = Math.floor((Math.random() * 6) + 1) as TValue;
if (value) rollValue = value;
if (cheatValue) rollValue = cheatValue;
setRolling(false);
setValue(rollValue);
if (diceAudio) diceAudio.pause();
if (!onRoll) return;
onRoll(rollValue);
}, rollingTime);
};
useImperativeHandle(ref, () => ({ rollDice: handleDiceRoll }));
...
return (
...
)
});
Your code then just needs to create a React ref and pass it to the Dice component, and instantiate the interval in a mounting useEffect hook.
Example:
function App() {
const diceRef = useRef();
useEffect(() => {
const rollDice = () => {
console.log("Rolling Dice");
diceRef.current.rollDice(); // <-- call rollDice function
};
// instantiate interval
setInterval(() => {
rollDice();
}, 10 * 1000);
// immediately invoke so we don't wait 10 seconds for first roll
rollDice();
}, []);
return (
<div className="App">
<Dice
ref={diceRef}
id="dice"
rollingTime={3000}
triggers={["click", "P"]}
/>
</div>
);
}

useState with an argument in it's array is breaking a setInterval and makes it glitchy and erratic

I just asked a question earlier here: react value of a state variable different in a different function
and now I have a new problem.
having a useEffect that looks like this
useEffect(() => {
countDown();
console.log('Score in useeffect', strokeScore);
}, [strokeScore]);
is breaking a setInterval that looks like this:
const countDown = () => {
// let strokeCountdown = Math.floor(Math.random() * 31) + 100;
let strokeCountdown = 20
let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
let strokeCountDownSpeed = strokeCountdownSpeedOptions[Math.floor(Math.random()*strokeCountdownSpeedOptions.length)];
let strokeCounter = setInterval(() => {
strokeCountdown--
setStrokeCountdown(strokeCountdown)
if (strokeCountdown === 0) {
endOfGameRound()
clearInterval(strokeCounter)
setTotalStrokeScore(strokeScore);
}
}, strokeCountDownSpeed)
}
The full component looks like this:
import React, { useEffect, useState } from 'react';
function ScoreCard() {
const [strokeScore, setStrokeScore] = useState(1);
const [totalStrokeScore, setTotalStrokeScore] = useState(1);
const [strokeCountdown, setStrokeCountdown] = useState();
const strokeCountdownDing = new Audio('/sounds/round-complete.mp3');
// make new variable, maybe?
let strokeScoreCount = 0;
const endOfGameRound = () => {
strokeCountdownDing.play();
document.getElementById('stroke-counter-button').disabled = true;
}
const addToStrokeScore = () => {
setStrokeScore(prev => prev + 1);
// prints the correct number
console.log('Score in function', strokeScore);
if (strokeCountdown === 0) {
endOfGameRound()
}
}
const subtractStrokeScore = () => {
setStrokeScore(strokeScore - 1);
}
const countDown = () => {
// let strokeCountdown = Math.floor(Math.random() * 31) + 100;
let strokeCountdown = 20
let strokeCountdownSpeedOptions = [1000, 500, 300, 200];
let strokeCountDownSpeed = strokeCountdownSpeedOptions[Math.floor(Math.random()*strokeCountdownSpeedOptions.length)];
let strokeCounter = setInterval(() => {
strokeCountdown--
setStrokeCountdown(strokeCountdown)
if (strokeCountdown === 0) {
endOfGameRound()
clearInterval(strokeCounter)
setTotalStrokeScore(strokeScore);
}
}, strokeCountDownSpeed)
}
useEffect(() => {
countDown();
console.log('Score in useeffect', strokeScore);
}, [strokeScore]);
return (
<div className="game__score-card">
<div className="game__speed-level">
Speed: idk
</div>
<div className="game__stroke-countdown">
Countdown: {strokeCountdown}
</div>
<p>Score: {strokeScore}</p>
<button id="stroke-counter-button" onClick={addToStrokeScore}>
{strokeCountdown === 0 ? 'Game Over' : 'Stroke'}
</button>
{/* window.location just temp for now */}
{strokeCountdown === 0
? <button onClick={() => window.location.reload(false)}>Play Again</button>
: <button disabled>Game in Progress</button>
}
<div className="game__total-score">
Total score: {totalStrokeScore}
</div>
</div>
);
}
export default ScoreCard;
When I click on the button, the timer gets erratic and goes all over the place.
All I want to do is make it so that the timer counts down smoothly, gets the clicks the user made and add it to total score.
Why is
useEffect(() => {
countDown();
console.log('Score in useeffect', strokeScore);
}, [strokeScore]);
Breaking everything?
I was calling countDown() everytime I clicked so I just did
useEffect(() => {
if (strokeScore === 1) {
countDown();
}
console.log('Score in useeffect', strokeScore);
}, [strokeScore]);

Countdown timer with react hooks

I'm trying to implement countdown timer on my own just to know hooks more. I know there are libraries out there but don't want to use it. the problem with my code is, I cannot get updated state inside "timer" function which is updated in start timer function I'm trying to implement timer that will have triggers to start, stop, & resume & can be manually trigger. by other component that is using the countdown component
import React, { useState } from 'react';
const Countdown = ({ countDownTimerOpt }) => {
const [getObj, setObj] = useState({
formatTimer: null,
countDownTimer: 0,
intervalObj: null,
});
const { formatTimer, countDownTimer, intervalObj } = getObj;
if (countDownTimerOpt > 0 && intervalObj === null) {
startTimer();
}
function startTimer() {
const x = setInterval(() => {
timer();
}, 1000);
setObj((prev) => ({
...prev,
countDownTimer: countDownTimerOpt,
intervalObj: x,
}));
}
function timer() {
var days = Math.floor(countDownTimer / 24 / 60 / 60);
var hoursLeft = Math.floor(countDownTimer - days * 86400);
var hours = Math.floor(hoursLeft / 3600);
var minutesLeft = Math.floor(hoursLeft - hours * 3600);
var minutes = Math.floor(minutesLeft / 60);
var remainingSeconds = countDownTimer % 60;
const formatTimer1 =
pad(days) +
':' +
pad(hours) +
':' +
pad(minutes) +
':' +
pad(remainingSeconds);
if (countDownTimer === 0) {
clearInterval(intervalObj);
} else {
setObj((prev) => ({
...prev,
formatTimer: formatTimer1,
countDownTimer: prev['countDownTimer'] - 1,
}));
}
}
function pad(n) {
return n < 10 ? '0' + n : n;
}
return <div>{formatTimer ? formatTimer : Math.random()}</div>;
};
export default Countdown;
import React, { useState, useEffect } from 'react';
import Timer from '../../components/countdown-timer/countdown.component';
const Training = () => {
const [getValue, setValue] = useState(0);
useEffect(() => {
const x = setTimeout(() => {
console.log('setTimeout');
setValue(10000);
}, 5000);
return () => clearInterval(x);
}, []);
return <Timer countDownTimerOpt={getValue} />;
don't want to use any set interval inside training page as the countdown component will also be used in exam page
Usually with hooks I would combine your functionality into a custom hook and use it in different places.
const useTimer = (startTime) => {
const [time, setTime] = useState(startTime)
const [intervalID, setIntervalID] = useState(null)
const hasTimerEnded = time <= 0
const isTimerRunning = intervalID != null
const update = () => {
setTime(time => time - 1)
}
const startTimer = () => {
if (!hasTimerEnded && !isTimerRunning) {
setIntervalID(setInterval(update, 1000))
}
}
const stopTimer = () => {
clearInterval(intervalID)
setIntervalID(null)
}
// clear interval when the timer ends
useEffect(() => {
if (hasTimerEnded) {
clearInterval(intervalID)
setIntervalID(null)
}
}, [hasTimerEnded])
// clear interval when component unmounts
useEffect(() => () => {
clearInterval(intervalID)
}, [])
return {
time,
startTimer,
stopTimer,
}
}
You can of course add a reset function or do other changes but use could look like this:
const Training = () => {
const { time, startTimer, stopTimer } = useTimer(20)
return <>
<div>{time}</div>
<button onClick={startTimer}>start</button>
<button onClick={stopTimer}>stop</button>
</>
}
You can create a useCountDown Hook as follow (In Typescript) :
Gist
import { useEffect, useRef, useState } from 'react';
export const useCountDown: (
total: number,
ms?: number,
) => [number, () => void, () => void, () => void] = (
total: number,
ms: number = 1000,
) => {
const [counter, setCountDown] = useState(total);
const [startCountDown, setStartCountDown] = useState(false);
// Store the created interval
const intervalId = useRef<number>();
const start: () => void = () => setStartCountDown(true);
const pause: () => void = () => setStartCountDown(false);
const reset: () => void = () => {
clearInterval(intervalId.current);
setStartCountDown(false);
setCountDown(total);
};
useEffect(() => {
intervalId.current = setInterval(() => {
startCountDown && counter > 0 && setCountDown(counter => counter - 1);
}, ms);
// Clear interval when count to zero
if (counter === 0) clearInterval(intervalId.current);
// Clear interval when unmount
return () => clearInterval(intervalId.current);
}, [startCountDown, counter, ms]);
return [counter, start, pause, reset];
};
Usage Demo: https://codesandbox.io/s/usecountdown-hook-56lqv

How to reset the timer using UseEffect React Hooks

I'm currently trying to make a pomodoro timer. One feature I noticed on some timers is that if you click reset time, it'll go to the default time (usually 15 minutes).
I want it to be a little more advanced so that when you click reset, it will reset to whatever value you set the timer to.
For example, when you open the screen the default time is 15 minutes. If you add 5 minutes, then press start, then press reset, it should reset to 20 minutes. Instead, my code is resetting it to 15.
Can anyone think of a way to reset time?
Apologies if it's a bad explanation. Thank you in advance.
Here's my code so far using React Hooks:
import './App.css';
function App() {
const [session, setSession] = useState(5)
const [timer, setTimer] = useState(2)
const [isRunning, setIsRunning] = useState(false)
const [resetTime, setResetTime] = useState(900)
let time = new Date(timer * 1000).toISOString().substr(11, 8);
function sessionIncrement() {
setSession(prevSession => session + 1)
}
function sessionDecrement() {
if (session > 0) {
setIsRunning(false)
setSession(prevSession => prevSession > 0 && prevSession - 1)
}
}
function resetTimer() {
setIsRunning(false)
setTimer(resetTime)
}
function increment() {
setIsRunning(false);
setTimer(prevTimer => prevTimer + 300);
}
function decrement() {
setIsRunning(false);
setTimer(prevTimer => prevTimer - 300);
}
useEffect(() => {
if (isRunning) {
const interval = setInterval(() => {
setTimer(prevTimer => prevTimer > 0 && prevTimer - 1)
}, 1000);
if (timer === 0) {
sessionDecrement()
setIsRunning(false)
}
return () => clearInterval(interval)
}
}, [isRunning, session, timer])
useEffect(() => {
setResetTime(timer)
}, [])
return (
<div className="App">
<h1>Session #{session}</h1>
<button onClick={() => sessionDecrement()}>-</button>
<button onClick={() => sessionIncrement()}>+</button>
<h1>{time}</h1>
<button onClick={() => setIsRunning(false)} >Pause</button>
<button onClick={() => setIsRunning(true)}>Start</button>
<button onClick={() => resetTimer()}>Reset</button>
<button onClick={() => decrement()}>-</button>
<button onClick={() => increment()}>+</button>
</div>
);
}
export default App;
The first thing you'll want to do is change the timer and reset time to use the same value:
const initialTime = 900;
const [session, setSession] = useState(5);
const [timer, setTimer] = useState(initialTime);
const [isRunning, setIsRunning] = useState(false);
const [resetTime, setResetTime] = useState(initialTime);
Then, you'll want to change increment and decrement functions so that they change the reset value as well as the timer value:
function increment() {
setIsRunning(false);
setTimer((prevTimer) => prevTimer + 300);
setResetTime((prevResetTime) => prevResetTime + 300);
}
function decrement() {
setIsRunning(false);
setTimer((prevTimer) => prevTimer - 300);
setResetTime((prevResetTime) => prevResetTime - 300);
}
If I understand you right, you just need to set resetTime to the same value as timer, in the increment and decrement function. Like this:
import './App.css';
function App() {
const [session, setSession] = useState(5)
const [timer, setTimer] = useState(2)
const [isRunning, setIsRunning] = useState(false)
const [resetTime, setResetTime] = useState(900)
let time = new Date(timer * 1000).toISOString().substr(11, 8);
function sessionIncrement() {
setSession(prevSession => session + 1)
}
function sessionDecrement() {
if (session > 0) {
setIsRunning(false)
setSession(prevSession => prevSession > 0 && prevSession - 1)
}
}
function resetTimer() {
setIsRunning(false)
setTimer(resetTime)
}
function increment() {
const newTime = timer + 300
setIsRunning(false);
setTimer(newTime);
setResetTime(newTime)
}
function decrement() {
const newTime = timer - 300
setIsRunning(false);
setTimer(newTime);
setResetTime(newTime)
}
useEffect(() => {
if (isRunning) {
const interval = setInterval(() => {
setTimer(prevTimer => prevTimer > 0 && prevTimer - 1)
}, 1000);
if (timer === 0) {
sessionDecrement()
setIsRunning(false)
}
return () => clearInterval(interval)
}
}, [isRunning, session, timer])
useEffect(() => {
setResetTime(timer)
}, [])
return (
<div className="App">
<h1>Session #{session}</h1>
<button onClick={() => sessionDecrement()}>-</button>
<button onClick={() => sessionIncrement()}>+</button>
<h1>{time}</h1>
<button onClick={() => setIsRunning(false)} >Pause</button>
<button onClick={() => setIsRunning(true)}>Start</button>
<button onClick={() => resetTimer()}>Reset</button>
<button onClick={() => decrement()}>-</button>
<button onClick={() => increment()}>+</button>
</div>
);
}
export default App;

Resources