setTimout with pause/resume counter not updating on render - javascript

I would like to setup a counter which can be paused as well as resumed in React.js. But whatever I have tried so far is working the functionality part (pause/resume is working) but it's not updating the counter on render. Below is my code:
const ProgressBar = (props) => {
const [isPlay, setisPlay] = useState(false);
const [progressText, setProgressText] = useState(
props.duration ? props.duration : 20
);
var elapsed,
secondsLeft = 20;
const timer = () => {
// setInterval for every second
var countdown = setInterval(() => {
// if allowed time is used up, clear interval
if (secondsLeft < 0) {
clearInterval(countdown);
return;
}
// if paused, record elapsed time and return
if (isPlay === true) {
elapsed = secondsLeft;
return;
}
// decrement seconds left
secondsLeft--;
console.warn(secondsLeft);
}, 1000);
};
timer();
const stopProgress = () => {
setisPlay(!isPlay);
if (isPlay === false) {
secondsLeft = elapsed;
}
};
return (
<>
<p>{secondsLeft}</p>
</>
);
};
export default ProgressBar;
I have tried React.js state, global var type, global let type, react ref so far to make the variable global but none of them worked..

So basically why does your example not work?
Your secondsLeft variable not connected to your JSX. So each time your component rerendered it creates a new secondsLeft variable with a value of 20 (Because rerendering is simply the execution of your function that returns JSX)
How to make your variable values persist - useState or useReducer hook for react functional component or state for class based one. So react will store all the values for you for the next rerender cycle.
Second issue is React doesn't rerender your component, it just doesn't know when it should. So what causes rerendering of your component -
Props change
State change
Context change
adding/removing your component from the DOM
Maybe I missing some other cases
So example below works fine for me
import { useEffect, useState } from "react";
function App() {
const [pause, setPause] = useState(false);
const [secondsLeft, setSecondsLeft] = useState(20);
const timer = () => {
var countdown = setInterval(() => {
if (secondsLeft <= 0) {
clearInterval(countdown);
return;
}
if (pause === true) {
clearInterval(countdown);
return;
}
setSecondsLeft((sec) => sec - 1);
}, 1000);
return () => {
clearInterval(countdown);
};
};
useEffect(timer, [secondsLeft, pause]);
const pauseTimer = () => {
setPause((pause) => !pause);
};
return (
<div>
<span>Seconds Left</span>
<p>{secondsLeft}</p>
<button onClick={pauseTimer}>{pause ? "Start" : "Pause"}</button>
</div>
);
}

import React, { useState, useEffect } from "react";
import logo from "./logo.svg";
import "./App.css";
var timer = null;
function App() {
const [counter, setCounter] = useState(0);
const [isplayin, setIsPlaying] = useState(false);
const pause = () => {
setIsPlaying(false);
clearInterval(timer);
};
const reset = () => {
setIsPlaying(false);
setCounter(0);
clearInterval(timer);
};
const play = () => {
setIsPlaying(true);
timer = setInterval(() => {
setCounter((prev) => prev + 1);
}, 1000);
};
return (
<div className="App">
<p>Counter</p>
<h1>{counter}</h1>
{isplayin ? (
<>
<button onClick={() => pause()}>Pause</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<>
{counter > 0 ? (
<>
<button onClick={() => play()}>Resume</button>
<button onClick={() => reset()}>Reset</button>
</>
) : (
<button onClick={() => play()}>Start</button>
)}
</>
)}
</div>
);
}
export default App;

Related

When I click on the exit button so that the timer stops. How to do that?

LabPart.jsx - Here, I want, when I click on the exit button, it will redirect me to the login page, and the timer will stop there, and when I log in from the login page, the timer will start from where it stopped. how do I do it. Here I have called clearInterval() to stop the timer, but it is not working. And after logging in, I can't even run it. please help me.
LabPart.jsx -
import React, {useState, useEffect} from 'react';
import Login from './Login';
const LabPart = ({Time , password}) => {
const [pass, setPass] = useState(password);
const [timeRemaining, setTimeRemaining] = useState(Time);
let stop = timeRemaining;
let interval = null;
useEffect(() => {
interval = setInterval(() => {
setTimeRemaining((time) => time - 1);
}, 1000);
return () => clearInterval(interval);
}, []);
const handleExitClick = () => {
console.log("logout successful");
clearInterval(interval);
setPass("");
};
// console.log(timeRemaining);
const hours = Math.floor(timeRemaining / 3600);
const minutes = Math.floor((timeRemaining % 3600) / 60);
const seconds = timeRemaining % 60;
return (
<>
{pass && timeRemaining>=0 ?
(<div>
<p>Time remaining: {hours}h {minutes}m {seconds}s</p>
<button onClick={handleExitClick}>Exit</button>
</div>)
: (<Login newTimeRemaining={stop} truee={true}/>)
}
</>
)
}
export default LabPart;
Login.jsx - After clicking the exit button, I want to show the time left on the login page.
import React, { useState, useEffect } from "react";
import LabPart from "./LabPart";
import '../style/login.css';
const Login = ({newTimeRemaining,truee} ) => {
const [password, setPassword] = useState("");
const [timeRemaining, setTimeRemaining] = useState(10);
const [openPage, setOpenPage] = useState(false);
// const [netTime, setNewtime] = useState(timeRemaining1);
const handlePasswordChange = (event) => {
setPassword(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
// setNewtime(timeRemaining1);
// if(newTimeRemaining<0)
// {
// setTimeRemaining(newTimeRemaining)
// }
if (password === "12345" ) {
console.log("login successful");
setOpenPage(true);
}
else {
alert("Incorrect Password")
}
};
return (
<>
{openPage ? <LabPart Time={10} password={password} />
:
<form onSubmit={handleFormSubmit}>
<input
type="password"
value={password}
onChange={handlePasswordChange}
/>
<button type="submit">Enter</button>
{
x ? newTimeRemaining : timeRemaining
}
</form>
}
</>
);
};
export default Login;
you should understand that react is very different (different from vue and angular) constructors will always callback when need to update without any cache create interval as a state
const [interval, setCInterval] = useState(null) //let interval = null;
useEffect(() => {
setCInterval(
setInterval(() => {
setTimeRemaining((time) => time - 1);
}, 1000)
)
return () => clearInterval(interval);
}, []);

The Timer functionality in react quiz application not resetting

I am trying to create a quiz application in react with timer. I have a timer component as shown in code:
import React from 'react'
import { useState } from 'react';
const CountDownTimer = ({hoursMinSecs,stopper}) => {
const { hours = 0, minutes = 0, seconds = 60 } = hoursMinSecs;
const [[hrs, mins, secs], setTime] = useState([hours, minutes, seconds]);
const [isActive, setIsActive] = useState(true);
function toggle() {
setIsActive(!isActive);
}
const tick = () => {
if (hrs === 0 && mins === 0 && secs === 0)
alert("TimeUP");
else if (mins === 0 && secs === 0) {
setTime([hrs - 1, 59, 59]);
} else if (secs === 0) {
setTime([hrs, mins - 1, 59]);
} else {
setTime([hrs, mins, secs - 1]);
}
};
const reset = () => setTime([parseInt(hours), parseInt(minutes), parseInt(seconds)]);
React.useEffect(() => {
let timerId=null;
if(isActive){
timerId = setInterval(() => tick(), 1000);
}
if(stopper==true){
clearInterval(timerId);
}
else if(stopper=="reset"){
setIsActive(true);
reset();
}
return () => clearInterval(timerId);
});
return (
<div>
<p>{`${hrs.toString().padStart(2, '0')}:${mins
.toString()
.padStart(2, '0')}:${secs.toString().padStart(2, '0')}`}</p>
</div>
);
}
export default CountDownTimer;
99% of code is taken from here Timer component.
Now I wanted to use this component in my quiz application which is basically fetching json data from an API call and sending it to a component. Sorry but the code is very unclean and cluttered.
import React from 'react'
import axios from 'axios'
import CountDownTimer from './CountDownTimer'
import { useEffect,useState } from 'react'
import Question from './Question'
const Quiz = () => {
const [hoursMinSecs,sethoursMinSecs] = useState({hours:0, minutes: 0, seconds: 10});
const [questions,setQuestions] = useState({q:{},idx:0,max:0});
const [hide,setHide] = useState(false);
const [stop,setStop] = useState(false);
const changeStop = (dat)=>{
setStop(dat);
var curr = {...questions};
curr.idx +=1;
setQuestions(curr);
setStop(false);
}
console.log("Array size is"+questions.max);
useEffect(()=>{
var config = {
method: 'get',
url: 'http://localhost:1337/Quizzes/617da24c8d757b423e0ae9a8',
headers: { }
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
setQuestions({q:response.data.QA,idx:0,max:response.data.QA.length});
})
.catch(function (error) {
console.log(error);
});
},[])
return (
<>
<div style={{display:hide ? "none":"block"}}>
<Question data={questions.q[questions.idx]} type="mcq" changer = {changeStop} />
</div>
<div>
<CountDownTimer hoursMinSecs={hoursMinSecs} stopper = {stop} />
</div>
</>
)
}
export default Quiz
Here is my question component
import React from 'react'
import { useState } from 'react';
const Question = (props) => {
const [ans,setans] = useState(0);
const changeOption = (e)=>{
console.log(e.target.value);
setans(e.target.value);
}
let ques;
let options;
let arr=[];
const check = ()=>{
props.changer(true);
if(ans==props.data.A){
alert("Hooray Correct")
}
else {alert("Wrong")}
}
console.log("Props data is"+JSON.stringify(props));
switch(props.type){
case "mcq":
ques = (<div>
Question: {props?.data?.Q}
</div>)
for(var key in props?.data?.O ){
arr.push({o:key,val:props?.data?.O[key]});
}
options = arr.map((e)=>{
return (<>{e.val}<input type="radio" value={e.o} name="abc"/><br/></>)
})
}
return (
<div>
{ques}
<div onChange={changeOption}>
{options}
</div>
<button onClick={check}>Check</button>
</div>
)
}
export default Question
Leave the logic for switch state and how and why I am displaying question in this way. What I want to achieve is that user will see a question and a 10 second timer will start. User will click on the answer and click next when timer will stop and user will be able to go to next question and again the timer will start with same time for example 10 second . But instead what is happening is that user clicks on answer,timer stops, gets to see if his answer is correct or not. The next question begins but the timer starts with leftover time. I want the time to reset but I am unable to figure out how to do it.The time should start again for the next question, instead it is being continued from there.
These two images will make you the Idea clear

why I can't stop interval in react functional component

In my code I have some problems with intervals. I have situation like this
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
function handleClick() {
const timer = setInterval(() => {
if (count >= 10) {
clearInterval(timer);
} else {
setCount((prevCount) => prevCount + 1);
}
}, 1000);
}
return (
<>
<button onClick={handleClick}>start</button>
<p>{count}</p>
</>
);
}
export default App;
what am trying to do is to start an interval when user click a button and stop it when counter reaches 10, but it never stops and debugger says that inside setInterval count is always 0. Does anybody know what is the problem? I also found that if I rewrite component to class component like this
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleClick() {
let myInterval = setInterval(() => {
if (this.state.count >= 10) {
clearInterval(myInterval);
} else {
this.setState((prevState) => ({
count: prevState.count + 1,
}));
}
}, 1000);
}
render() {
return (
<>
<button onClick={this.handleClick.bind(this)}>start</button>
<p>{this.state.count}</p>
</>
);
}
}
it works perfectly.
I don't know what's happening and I spend almost day trying to figure out.
So does anybody know why class component works and why functional component doesn't?
Thanks in advance
You're seeing a stale count in the setInterval callback function since it captures the value at the time you're starting the timer.
You can use the "ref boxing" pattern to have a readable reference to the latest value of the state atom:
import {useState, useRef} from 'react';
function App() {
const [count, setCount] = useState(0);
const countRef = useRef(null);
countRef.current = count; // Update the boxed value on render
function handleClick() {
const timer = setInterval(() => {
if (countRef.current >= 10) { // Read the boxed value
clearInterval(timer);
} else {
setCount((prevCount) => prevCount + 1);
}
}, 1000);
}
return (
<>
<button onClick={handleClick}>start</button>
<p>{count}</p>
</>
);
}
Note that you're currently not correctly cleaning up the timer on unmount, leading to React warning you about that when you'd unmount your app. The same holds for your class component.

SetInterval on mount for a set duration

I have gone through some Q&As here but havent been able to understand what I am doing wrong. The following component prints 0s in console and does not update the DOM as expected.
const NotifPopup = ({ notif, index, closeHandler }) => {
const [timer, setTimer] = useState(0);
useEffect(() => {
const timerRef = setInterval(() => {
if (timer === 3) {
clearInterval(timerRef);
closeHandler(index);
} else {
console.log("timer", timer);
setTimer(timer + 1);
}
}, 1000);
}, []); // only run on mount
return (<div className="notifPopup">
<span className=""></span>
<p>{notif.message}</p>
<span className="absolute bottom-2 right-8 text-xs text-oldLace">{`closing in ${timer}s`}</span>
</div>);
};
Why is the setInterval printing a stream of 0s in console and not updating the DOM?
You are loggin, comparing, and setting a stale value due to closures.
See more use cases in a related question.
useEffect(() => {
// timerRef from useRef
timerRef.current = setInterval(() => {
setTimer((prevTimer) => prevTimer + 1);
}, 1000);
}, []);
useEffect(() => {
console.log("timer", timer);
if (timer === 3) {
clearInterval(timerRef.current);
}
}, [timer]);
Check out the code for useInterval in react-use. Inspecting the different hooks in this package can greatly improve your hooks understanding.
import { useEffect, useRef } from 'react';
const useInterval = (callback: Function, delay?: number | null) => {
const savedCallback = useRef<Function>(() => {});
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
if (delay !== null) {
const interval = setInterval(() => savedCallback.current(), delay || 0);
return () => clearInterval(interval);
}
return undefined;
}, [delay]);
};
export default useInterval;
And the usage as described in the docs:
import * as React from 'react';
import {useInterval} from 'react-use';
const Demo = () => {
const [count, setCount] = React.useState(0);
const [delay, setDelay] = React.useState(1000);
const [isRunning, toggleIsRunning] = useBoolean(true);
useInterval(
() => {
setCount(count + 1);
},
isRunning ? delay : null
);
return (
<div>
<div>
delay: <input value={delay} onChange={event => setDelay(Number(event.target.value))} />
</div>
<h1>count: {count}</h1>
<div>
<button onClick={toggleIsRunning}>{isRunning ? 'stop' : 'start'}</button>
</div>
</div>
);
};
To start the interval on mount simply change the value of isRunning on mount:
useMount(()=>{
toggleIsRunning(true);
});

How can I have an image that changes every 5 seconds in react.js?

import React, { useEffect, useState } from "react";
import aa from '../imgs/aa.png'
import aa2 from '../imgs/aa2.JPG'
import aa3 from '../imgs/aa3.JPG'
import aa4 from '../imgs/aa4.JPG'
import './AnimatedGalery.css'
export default function () {
return (
<div>
</div>
)
}
I have no idea how to start with this. I basically want an image (that I can resize and give css properties) that changes every 5 seconds to another one of those 4 imported images I have here.
The idea: put all imported images in a list. Have a state variable for the current image that should be displayed. Set an intervall that executes every 5 seconds in a useEffect that sets the new state with a randomly picked image.
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentImage, setCurrentImage] = useState(null);
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentImage(images[Math.floor(Math.random() * items.length)]);
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={currentImage} />
</div>
)
}
If you want to have a rotation of your images, then I would just save the currentIndex and display its image:
const images = [aa, aa2, aa3, aa4];
export default function ImageSwapper() {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
if(currentIndex === images.length - 1) {
setCurrentIndex(0);
}
else {
setCurrentIndex(currentIndex + 1);
}
}, 5000)
return () => clearInterval(intervalId);
}, [])
return (
<div>
<img src={images[currentIndex]} />
</div>
)
}
You can define a function to get current image, and use setInterval() to change image every five seconds, which you can define inside useEffect when the component renders:
const getPicture = index => {
switch (index) {
case 1:
return "'../imgs/aa.png";
case 2:
...
default:
break;
}
};
const NUMBER_OF_PICTURES = 4;
export default function Menu() {
const [index, setIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setIndex(prevIndex => (index == NUMBER_OF_PICTURES ? 0 : prevIndex + 1));
}, 5000);
return () => {
/* cleanup */
clearInterval(timer);
};
/* on component render*/
}, []);
return (
<>
<img src={getPicture(index)} />
</>
);
}
you can add all the images name in a array like this :-
const temp =[aa,aa2,aa3,aa4]
const [counter,setCounter] = useState(0);
setInterval(function(){
counter === 4 ? setCounter(0) : setCounter(counter + 1);
}, 5000);
In tag you can use it like this
<img src=`{temp[counter]}` />

Categories

Resources