How to display countdown timer in React - reactjs

How can I show countdown timer in minute and seconds. right now I am able to show the timer in seconds, only but I want to display both minutes and seconds both.
Currently my countdown timer is showing in this way Countdown: 112 but I want it to be like Countdown: 1: 52
import React from "react";
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}

Here's a complete solution with formatting time:
// Prepend `0` for one digit numbers. For that the number has to be
// converted to string, as numbers don't have length method
const padTime = time => {
return String(time).length === 1 ? `0${time}` : `${time}`;
};
const format = time => {
// Convert seconds into minutes and take the whole part
const minutes = Math.floor(time / 60);
// Get the seconds left after converting minutes
const seconds = time % 60;
//Return combined values as string in format mm:ss
return `${minutes}:${padTime(seconds)}`;
};
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
let timer;
if (counter > 0) {
timer = setTimeout(() => setCounter(c => c - 1), 1000);
}
return () => {
if (timer) {
clearTimeout(timer);
}
};
}, [counter]);
return (
<div className="App">
{counter === 0 ? "Time over" : <div>Countdown: {format(counter)}</div>}
</div>
);
}
A few notes about your original code:
Since the next value of counter depends on the previous one it's better to use the functional form of setState.
It's a good practice to clear timeout when component unmounts.

Import hooks from import stage:
Hooks react
import React, { useState, useEffect } from "react";
export default function App() {
const [counter, setCounter] = useState(120);
useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}

Changing your setCounter method to following should work.
React.useEffect(() => {
counter !== 'Time Over' && setTimeout(() => setCounter(counter > 1 ? counter - 1 : 'Time Over'), 1000);
}, [counter]);

This can be done like so:
import React from "react";
export default function App() {
const [counter, setCounter] = React.useState(120);
React.useEffect(() => {
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
}, [counter]);
return (
<div className="App">
<div>Countdown: {counter === 0 ? "Time over" : counter}</div>
</div>
);
}

Related

Prevent fetching when component re-renders

I have this simple website where I fetch data and get an array of Obj, and display it on the Quiz page, but I also implement a countdown. The problem is that every time the timer renders the fetch run again and I get a different array every sec. How Do I prevent the initial obj I fetch from changing?
Im getting the fetch array from useContext
const Quiz = () =>{
useEffect(() => {
countDown();
}, [num]);
let timer;
const countDown = () => {
if (num > 0) {
timer = setTimeout(() => {
setNum(num - 1);
}, 1000);
}
if(num === 0){
nextQuestion()
}
return num;
};
return(...)
}
You need to remove the num from the dependency array of useEffect.
When you add a dependency to useEffect it will re render every time it changes, and you only want countDown to run once.
Usually you should also wrap the countDown component with useCallback and put the countDown as a dependency on the useEffect
anyway, for now this should solve your issue -
const Quiz = () =>{
useEffect(() => {
countDown();
}, []);
let timer;
const countDown = () => {
if (num > 0) {
timer = setTimeout(() => {
setNum(num - 1);
}, 1000);
}
if(num === 0){
nextQuestion()
}
return num;
};
return(...)
}
does this answer your question ?
import React, { useState, useEffect } from "react";
const questionsList = [
{ question: "Sum of 4+4 ?" },
{ question: "Sum of 10+10 ?" }
];
export default function CountDown() {
const [counter, setCounter] = useState(10);
const [currentQuestion, setCurrentQuestion] = useState(0);
useEffect(() => {
const timeInterval = setInterval(() => {
counter > 0 && setCounter((prevCount) => prevCount - 1);
}, 1000);
if (counter === 0 && currentQuestion + 1 !== questionsList.length) {
setCurrentQuestion((prevQues) => prevQues + 1);
setCounter(10);
}
return () => {
clearInterval(timeInterval);
};
}, [counter]);
return (
<>
<h1>CountDown {counter}</h1>
<h1>
{counter !== 0 ? (
<>
{" "}
Question number {currentQuestion + 1}
{" --> "}
{questionsList?.[currentQuestion]?.question}
</>
) : (
<h1>Test Ended </h1>
)}
</h1>
</>
);
}

How to clearInterval correctly using React Native

In RN, I have a countdown timer using setInterval that goes from 10 - 0.
Once the condition is met of the time === 0 or less than 1, I want the interval to stop.
The countdown is working but is repeating continuously, clearInterval not working.
What am I doing wrong?
import { StyleSheet, Text } from 'react-native'
import React, {useEffect, useState} from 'react'
export default function Timer() {
const [time, setTime] = useState(10)
useEffect(() => {
if(time > 0) {
var intervalID = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
} else {
clearInterval(intervalID)
}
}, [])
return <Text style={styles.timer}>{time}</Text>
}
ClearInterval should be inside, setTime because useEffect will only trigger once.
NOTE: you should also clear on unmount.
export default function Timer() {
const [time, setTime] = useState(10);
useEffect(() => {
var intervalID = setInterval(() => {
setTime((time) => {
if (time > 0) {
return time - 1;
}
clearInterval(intervalID);
return time;
});
}, 1000);
return () => clearInterval(intervalID);
}, []);
return <Text style={styles.timer}>{time}</Text>;
}
The function is on useEffect that only get executed when the component is mounted/unmounted, at the moment where the function is executed, time is 10 so will never go into else condition.
This code must work for you:
import { StyleSheet, Text } from 'react-native'
import React, {useEffect, useState} from 'react'
export default function Timer() {
const [time, setTime] = useState(10)
time <= 0 && clearInterval(intervalID)
useEffect(() => {
var intervalID = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
return () => {
clearInterval(intervalID)
}
}, [])
return <Text style={styles.timer}>{time}</Text>
You can use this also:
import React, { useEffect, useState, useRef } from 'react'
import { Text } from 'react-native'
const Timer = () => {
const [time, setTime] = useState(10)
const promiseRef = useRef(null)
useEffect(() => {
if(time > 0) {
promiseRef.current = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
} else {
promiseRef.current = null
}
}, [])
return <Text style={styles.timer}>{time}</Text>
}
export default Timer
Your useEffect does not have any value(time) inside the dependency array, so it will run only once on component mount.
const [time, setTime] = useState(10);
useEffect(() => {
if (time === 0) {
return;
}
const timeoutId = setInterval(() => {
setTime(time - 1);
}, 1000);
return () => {
clearInterval(timeoutId);
};
}, [time]);
You could also use timeout, where you don't need to clear anything as it will run only once per useEffect trigger.
useEffect(() => {
if (time === 0) {
return;
}
setTimeout(() => {
setTime(time - 1);
}, 1000);
}, [time]);
Another option that might work if you don't want to add time to the dependency array is to clear the interval inside the setTime
useEffect(() => {
const intervalID = setInterval(() => {
setTime((time) => {
if (time === 0) {
clearInterval(intervalID);
return time;
}
return time - 1;
});
}, 1000);
return () => {
clearInterval(intervalID);
};
}, []);
Note that in your example you are counting down to 1 and the going back to 10, so it will never reach 0
setTime(time => time > 0 ? time - 1 : time = 10)
if you want it to reset back to 10 after it finishes count the last option might work for you, as it won't trigger the effect on every time change, just need to return 10 instead of time after clearing the interval
Here is an example https://jsfiddle.net/dnyrm68t/

unable to update name and value every 10 seconds

I am trying to implement react counter. every 10 seconds I need to update label and progress bar. But in label I could able to display 1 to 6 in 60 seconds successfully. but in timer due to some issue even though it reaches 60 seconds progress bar percange showing 80% only.
timer logic
const [number, setNumber] = useState(0);
const [progBarVal, setProgBarValr] = useState(0);
useEffect(() => {
if (number >= 6) {
return;
}
const intervalID = setTimeout(() => {
setNumber((t) => t + 1);
setProgBarValr((t) => t + 10);
}, 1000);
return () => clearInterval(intervalID);
}, [number, progBarVal]);
with in the return statement
return{
<div > {number}/6 </div>
<Progress done={progBarVal} />
}
progress bar logic
import React, { useState } from 'react';
import './progress.scss';
const Progress = ({ done }) => {
const [style, setStyle] = useState({});
setTimeout(() => {
const newStyle = {
opacity: 1,
width: `${done}%`,
};
setStyle(newStyle);
}, 200);
return (
<div className='met-prog__progress'>
<div className='met-prog__progress-done' style={style}>
{done}%
</div>
</div>
);
};
export default Progress;
I am trying to do if number is 1 the progBarVal 10 like that.
someone help me to understand where it went wrong.

I want to subtract the value of 'percent' from the function by 0.25 in React

I want to subtract the value of 'percent' from the function by 0.25.
However, subtraction does not work.
I used setState, but I don't know why it doesn't work.
import React, {useState, useRef, useCallback} from 'react';
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback(() =>{
if (intervalRef.current !== null){
return;
}
intervalRef.current = setInterval(()=>{
if (percent > 0){
setPercent(c => c - 0.25);
console.log("percent = ", percent);
}
else {
setPercent(c => 1);
}
}, 1000);
}, []);
return (
<div>
<button onClick={()=>{start()}}>{"Start"}</button>
</div>
);
}
export default Ques;
Issue
The enqueued state updates are working correctly but you've a stale enclosure over the percent state in the interval callback that you are logging, it never will update.
Solution
If you want to log the percent state then use an useEffect hook to log changes.
const Ques = () => {
const [percent, setPercent] = useState(1);
const intervalRef = useRef(null);
useEffect(() => {
console.log("percent = ", percent); // <-- log state changes here
}, [percent]);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
setPercent((c) => Math.max(0, c - 0.25)); // <-- simpler updater function
}, 1000);
}, []);
return (
<div>
Percent: {percent * 100}
<button onClick={start}>Start</button>
</div>
);
};
You can create a ref for percent also and chenge its current value as:
codesandbox link
import React, { useRef, useCallback } from "react";
const Ques = () => {
const percentRef = useRef(1);
const intervalRef = useRef(null);
const start = useCallback(() => {
if (intervalRef.current !== null) {
return;
}
intervalRef.current = setInterval(() => {
console.log("percent = ", percentRef.current);
percentRef.current > 0
? (percentRef.current -= 0.25)
: (percentRef.current = 1);
}, 1000);
}, []);
return (
<div>
<button onClick={start}>Start</button>
</div>
);
};
export default Ques;
I think useCallback and useRef is not a good fit. Below is a minimal verifiable example using useState and useEffect. Note this function appropriately performs cleanup on the timer when the component is unmounted. Click Run to run the code snippet and click start to begin running the effect.
function App() {
const [percent, setPercent] = React.useState(1)
const [running, setRunning] = React.useState(false)
React.useEffect(() => {
if (!running) return
const t = window.setTimeout(() => {
setPercent(c => c > 0 ? c - 0.25 : 1)
}, 1000)
return () => window.clearTimeout(t)
}, [running, percent])
return <div>
<button onClick={() => setRunning(true)} children="start" />
<pre>percent: {percent}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
This may be one possible solution to achieve what is presumed to be the desired objective:
Code Snippet
const {useState, useRef, useCallback} = React;
const Ques = () => {
const [percent,setPercent] = useState(1);
const intervalRef = useRef(null);
const start = useCallback((flag) => {
if (intervalRef.current !== null){
if (flag && flag === 'end') clearInterval(intervalRef.current);
return;
}
intervalRef.current = setInterval(() => {
setPercent(
prev => (prev > 0 ? prev - 0.25 : 1)
);
}, 1000);
}, []);
return (
<div>
percent: {percent} <br/> <br/>
<button onClick={() => start('bgn')}>Start</button>  
<button onClick={() => start('end')}>Stop</button>
</div>
);
}
ReactDOM.render(
<div>
<h3>DEMO</h3>
<Ques />
</div>,
document.getElementById('rd')
);
<div id='rd' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
There are two buttons Start and Stop
Both invoke the same start method, but with different params (flag)
If intervalRef is already set (ie, not null) and flag is end, clear the interval
The percent is added to the UI to see real-time changes to its value
setPercent is modified to use prev (which holds the correct state)

How to increment a React state every second using setInterval?

I am trying to update (increment) a React state (counter) every setInterval function but it is don't work as expected. For the first 5 or so seconds, the count is rendered with consistent increments but after that it increases randomly and doesn't stop.
export default function App() {
const [count, setCount] = useState(0);
setInterval(() => setCount((oldCount) => oldCount + 1), 1000);
return (<>
<div>{count}</div>
</>);
};
How can I achieve this and the same for time intervals less than a second ( 100 or 10ms ) ?
You need to run the interval in useEffect there are plenty of such examples:
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount((oldCount) => oldCount + 1), 1000);
return () => {
clearInterval(id);
};
}, []);
return (
<>
<div>{count}</div>
</>
);
}

Resources