I am trying to implement counter in React.js which increments the value continuously on click but I am not getting the appropriate result, code works fine in plain html/js.
https://codesandbox.io/s/autumn-wood-fhsjx?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState(0);
const continuosIncerment = () => {
console.log(`Setting ${value}`);
setValue(value + 1);
timer = setTimeout(function() {
continuosIncerment();
}, 1000);
};
function timeoutClear() {
clearTimeout(timer);
}
return (
<div className="App">
<button
onMouseLeave={timeoutClear}
onMouseUp={timeoutClear}
onMouseDown={continuosIncerment}
>
Increment
</button>
<div>Value = {value} </div>
</div>
);
}
You can use setInterval, useRef and callback approach to update state to solve your issue.
Working demo
Code snippet
export default function App() {
const [value, setValue] = useState(0);
const timer = useRef(null);
const increment = () => {
timer.current = setInterval(() => setValue(prev => prev + 1), 500);
};
function timeoutClear() {
clearInterval(timer.current);
}
return (
<div className="App">
<button
onMouseLeave={timeoutClear}
onMouseUp={timeoutClear}
onMouseDown={increment}
>
Increment
</button>
<div>Value = {value} </div>
</div>
);
}
This is the code for a timer that I used on my app, maybe it can help you.
import React, { useState, useEffect } from "react";
import "./clock.css";
function Clock() {
const [isRunning, setIsRunning] = useState(false);
const [seconds, setSeconds] = useState(0);
const [minutes, setMinutes] = useState(0);
const [hours, setHours] = useState(0);
const toggleOn = () => {
setIsRunning(!isRunning);
};
const reset = () => {
setMinutes(0);
setSeconds(0);
setIsRunning(false);
setHours(0);
};
useEffect(() => {
let interval = null;
if (isRunning) {
interval = setInterval(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
if (seconds > 60) {
setMinutes((minutes) => minutes + 1);
setSeconds(0);
}
if (minutes > 60) {
setHours((hours) => {
return hours + 1;
});
setMinutes(0);
}
} else if (!isRunning && seconds !== 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isRunning, seconds]);
const formatedTime = () => {
let formatedSeconds = seconds < 10 ? `0${seconds}` : seconds;
let formatedMinutes = () => {
if (hours >= 1) {
if (minutes < 10) {
return `0${minutes}`;
} else return minutes;
} else if (hours < 1) {
return minutes;
}
};
let formatedHours = hours < 1 ? " " : `${hours}:`;
let formatedTime = `${formatedHours}${formatedMinutes()}:${formatedSeconds}`;
return formatedTime;
};
return (
<div className="clock">
<h1>{formatedTime()}</h1>
<div>
<button className="btn btn-primary m-1" onClick={toggleOn}>
{isRunning ? "Pause" : "Start"}
</button>
<button className="btn btn-primary m-1" onClick={reset}>
Reset
</button>
</div>
</div>
);
}
export default Clock;
Related
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);
}, []);
I am making a countdown timer script in react.js.
Before the timer starts, a 3 or 5 seconds countdown is displayed, data for both countdown becomes from another component.
I am trying to stop/pause main countdown with a button.
My problem is how can I control data from a function that is executed by useEffect()?
I am trying crating a state for the btn, but the scope for the state is the useEffect
import TimerForm from './components/TimerForm';
const CountDown = (props) => {
const [timeLeft, setTimeLeft] = useState({
minutes: 0,
seconds: 0
});
const [counter, setCounter] = useState();
const [startTimer, setStartTimer] = useState(false);
const [result, setResult] = useState();
let Interval;
useEffect(() => {
let count = null;
if(startTimer){
if(counter === 0){
relog(result);
clearInterval(count);
return
}
count = setInterval(() => {
setCounter((prevcounter) => prevcounter - 1);
}, 1000);
return () => clearInterval(count);
} else {
clearInterval(count);
}
}, [startTimer, counter]);
const relog = useCallback((ForTime) => {
console.log(testing);
Interval = setInterval(() => {
setTimeLeft({
seconds: ForTime % 60,
minutes: Math.floor(ForTime / 60)% 60
});
if(ForTime === 0){
clearInterval(Interval);
return;
}
ForTime--;
},1000);
setStartTimer(false);
},[]);
const timerSettings = (data) => {
setCounter(data.counter);
setResult(data.result);
setStartTimer(true);
}
return (
<div>
<section>
<TimerForm onTimerSettings={timerSettings} />
<span>{counter}</span>
<div className="TimerClock">
<span>{timeLeft.minutes}</span><span>:</span><span>{timeLeft.seconds}</span>
</div>
<div>
<button type="button" className="btn btn-stop">Pause Button</button>
</div>
</section>
</div>
)
};
export default CountDown;```
create a useInterval hook used by useEffect() and useRef().
/**
*
* #param {Function} callback
* #param {number|null} delay, stopped when delay is null
*/
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
use useInterval:
function App() {
const [delay, setDelay] = useState(null);
const [counter, setCounter] = useState(0);
useInterval(() => {
setCounter((counter) => counter - 1);
}, delay);
const handleChangeDelay = () => {
// stopped when delay is null
setDelay(delay === null ? 1000 : null);
};
return (
<div className="App">
<p>
<button onClick={handleChangeDelay}>trigger interval countdown</button>
</p>
<p>{counter}</p>
</div>
);
}
You can store the interval timer by using useRef
const countRef = useRef(null);
useEffect(() => {
if (startTimer) {
if (counter === 0) {
relog(result);
clearInterval(countRef.current);
return;
}
countRef.current = setInterval(() => {
setCounter((prevcounter) => prevcounter - 1);
}, 1000);
return () => clearInterval(countRef.current);
} else {
clearInterval(countRef.current);
}
}, [startTimer, counter]);
And you can get the timer from the useEffect scope
return (
...
<button type="button" className="btn btn-stop" onClick={()=>{clearInterval(countRef.current)}}>Pause Button</button>
...
)
I'm new to reactjs; I encountered this problem while studying about useState. I'm trying to decrease the value of the second state when the first state decreases to 0, and the iteration will run until both states are 0. But the second state always decreases by 2, which makes me confused.
This is my code:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [firstCount,setFirstCount]=useState(10);
const [secondCount,setSecondCount] = useState(5);
function decreaseCount(){
const interval= setInterval(()=>{
setFirstCount((prevFirstCount)=>{
if(prevFirstCount>0){
return prevFirstCount-1;
}
else{
setSecondCount((prevSecondCount)=>{
if(prevSecondCount>0){
return prevSecondCount-1
}
else{
clearInterval(interval);
return prevFirstCount
}
})
return 10;
}
})
},1000)
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={(decreaseCount)}>Decrease Count</button>
</div>
);
}
codesandbox link: https://codesandbox.io/s/interval-setcountprob-plpzl?file=/src/App.js:0-835
I'd really appreciate if someone can help me out.
It's because the callback you pass to setFirstCount must be pure, but you violated that contract by trying to use it to mutate secondCount. You can correctly implement this dependency with useRef and useEffect:
export default function App() {
const [firstCount, setFirstCount] = useState(0);
const [secondCount, setSecondCount] = useState(6);
const firstCountRef = useRef(firstCount);
const secondCountRef = useRef(secondCount);
firstCountRef.current = firstCount;
secondCountRef.current = secondCount;
function decreaseCount() {
const interval = setInterval(() => {
if (secondCountRef.current === 0) {
clearInterval(interval);
return;
}
const { current } = firstCountRef;
setFirstCount(prev => (prev + 9) % 10);
setSecondCount(prev => current > 0 ? prev : (prev + 9) % 10);
}, 1000);
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={decreaseCount}>Decrease Count</button>
</div>
);
}
However, it might be easier to use a single state and compute the counts from that:
export default function App() {
const [count, setCount] = useState(60);
const countRef = useRef(count);
const firstCount = count % 10;
const secondCount = Math.floor(count / 10);
countRef.current = count;
function decreaseCount() {
const interval = setInterval(() => {
if (countRef.current === 0) {
clearInterval(interval);
return;
}
setCount(prev => prev - 1);
}, 1000);
}
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={decreaseCount}>Decrease Count</button>
</div>
);
}
I solved the issue this way :
const [firstCount, setFirstCount] = useState(10);
const [secondCount, setSecondCount] = useState(5);
const handleDecrease = () => {
setInterval(() => {
setFirstCount((prev) => {
if (prev > 0) {
return prev - 1;
}
if (prev === 0) {
return prev + 10;
}
});
}, 1000);
};
React.useEffect(() => {
if (firstCount === 0) {
setSecondCount((prev) => {
if (prev === 0) {
setFirstCount((firstPrev) => firstPrev + 10);
return prev + 5;
} else {
return prev - 1;
}
});
}
}, [firstCount]);
return (
<div className="App">
<div>{firstCount}</div>
<div>{secondCount}</div>
<button onClick={handleDecrease}>Decrease Count</button>
</div>
);
You shouldn't declare functions like this:
function decreaseCount(){
...
Instead you should use useCallback:
const decreaseCount = useCallback(() => {
//your code here
}[firstCount, secondCount]) //dependency array
You should read more about hooks: https://reactjs.org/docs/hooks-intro.html
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;
I am unable to stop the loop after a conditional. I am able to stop the interval after a button click but unable to stop it after a conditional such as loop increments. This simple example tries to stop the interval loop after 5 loops.
Any solutions would be much appreciated!
import React, { useState } from 'react';
let gameLoop: any;
function App() {
const [loopCount, setLoopCount] = useState(0);
const [running, setRunning] = useState(true);
const gameLogic = () => {
console.log('Game logic!')
}
const loop = () => {
gameLogic();
setLoopCount(prev => {
const newCount = prev + 1;
console.log(newCount)
return newCount
});
// Stop the loop on a conditional
if(loopCount >= 5){
clearInterval(gameLoop)
}
}
const handleStartButtonClick = () => {
gameLoop = setInterval(loop, 1000)
setRunning(true);
}
const handleStopButtonClick = () => {
clearInterval(gameLoop);
setRunning(false);
}
const handleResetButtonClick = () => {
setLoopCount(0);
console.clear();
}
return (
<div className="App">
<div>
<button onClick={handleStartButtonClick}>Start</button>
<button onClick={handleStopButtonClick}>Stop</button>
<button onClick={handleResetButtonClick}>Reset</button>
</div>
</div>
);
}
export default App;
The solution is to put the conditional at the component level, not in the loop method.
import React, { useState } from 'react';
let gameLoop: any;
function App() {
const [loopCount, setLoopCount] = useState(0);
const [running, setRunning] = useState(true);
const gameLogic = () => {
console.log('Game logic!')
}
const loop = () => {
gameLogic();
setLoopCount(prev => {
const newCount = prev + 1;
console.log(newCount)
return newCount
});
}
//MOVE OUTSIDE GAME LOOP
// Stop the loop on a conditional
if(loopCount >= 5){
clearInterval(gameLoop)
}
const handleStartButtonClick = () => {
gameLoop = setInterval(loop, 1000)
setRunning(true);
}
const handleStopButtonClick = () => {
clearInterval(gameLoop);
setRunning(false);
}
const handleResetButtonClick = () => {
setLoopCount(0);
console.clear();
}
return (
<div className="App">
<div>
<button onClick={handleStartButtonClick}>Start</button>
<button onClick={handleStopButtonClick}>Stop</button>
<button onClick={handleResetButtonClick}>Reset</button>
</div>
</div>
);
}
export default App;