I wanted to build a timer application in React
using functional component
and below are the requirements.
The component will display a number initialized to 0
know as counter
.
The component will display a Start
button below the counter
number.
On clicking the Start
button the counter will start running. This means the counter
number will start incrementing by 1 for every one second.
When the counter is running(incrementing), the Start
button will become the Pause
button.
On clicking the Pause
button, the counter
will preserve its value (number) but stops running(incrementing).
The component will also display a Reset
button.
On clicking the Reset
button, the counter
will go to its initial value(which is 0
in our case) and stops running(incrementing).
Below is the code that I have implemented, but clearInterval
doesn't seems to be working, Also how do i implement Reset Button?
Code:
import React, { useState, useEffect } from "react";
export default function Counter() {
const [counter, setCounter] = useState(0);
const [flag, setFlag] = useState(false);
const [isClicked, setClicked] = useState(false);
var myInterval;
function incrementCounter() {
setClicked(!isClicked);
if (flag) {
myInterval = setInterval(
() => setCounter((counter) => counter 1),
1000
);
setFlag(false);
} else {
console.log("sasdsad");
clearInterval(myInterval);
}
}
function resetCounter() {
clearInterval(myInterval);
setCounter(0);
}
useEffect(() => {
setFlag(true);
}, []);
return (
<div>
<p>{counter}</p>
<button onClick={incrementCounter}>
{isClicked ? "Pause" : "Start"}
</button>
<button onClick={resetCounter}>Reset</button>
</div>
);
}
Codesandbox link: CodeSandbox
CodePudding user response:
You have to store myInterval
in state. After that when button is clicked and flag
is false
, you can clear interval (myInterval in state).
CodePudding user response:
Use useRef
to make the interval as a ref. Then use resetCounter()
to clean the interval ref.
const intervalRef = useRef(null)
const incrementCounter = () => {
intervalRef.current = setInterval(() => {
setCounter(prevState => prevState 1)
}, 1000);
};
const resetCounter = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
};
CodePudding user response:
I did a slightly different version that use an extra useEffect
that runs on isRunning
(changed name from flag
) change:
import React, { useState, useEffect, useRef } from "react";
export default function Counter() {
const [counter, setCounter] = useState(0);
// Change initial value to `false` if you don't want
// to have timer running on load
// Changed `flag` name to more significant name
const [isRunning, setIsRunning] = useState(false);
// You don't need 2 variable for this
//const [isClicked, setClicked] = useState(false);
// Using `useRef` to store a reference to the interval
const myInterval = useRef();
useEffect(() => {
// You had this line to start timer on load
// but you can just set the initial state to `true`
//setFlag(true);
// Clear time on component dismount
return () => clearInterval(myInterval.current);
}, []);
// useEffect that start/stop interval on flag change
useEffect(() => {
if (isRunning) {
myInterval.current = setInterval(
() => setCounter((counter) => counter 1),
1000
);
} else {
clearInterval(myInterval.current);
myInterval.current = null;
}
}, [isRunning]);
// Now on click you only change the flag
function toggleTimer() {
setIsRunning((isRunning) => !isRunning);
}
function resetCounter() {
clearInterval(myInterval.current);
myInterval.current = null;
setCounter(0);
setIsRunning(false);
}
return (
<div>
<p>{counter}</p>
<button onClick={toggleTimer}>{isRunning ? "Pause" : "Start"}</button>
<button onClick={resetCounter}>Reset</button>
</div>
);
}
Demo: https://codesandbox.io/s/dank-night-wwxqz3?file=/src/Counter.js
As a little extra i've made a version that uses a custom hook useTimer
. In this way the component code is way cleaner:
https://codesandbox.io/s/agitated-curie-nkjf62?file=/src/Counter.js
CodePudding user response:
Between each rendering your variable myInterval
value doesn't survive. That's why you need to use the [useRef
][1] hook that save the reference of this variable across each rendering.
Besides, you don't need an flag function, as you have all information with the myClicked
variable
Here is a modification of your code with those modifications. Don't hesitate if you have any question.
import React, { useState, useEffect, useRef } from "react";
export default function Counter() {
const [counter, setCounter] = useState(0);
const [isStarted, setIsStarted] = useState(false);
const myInterval = useRef();
function start() {
setIsStarted(true);
myInterval.current = setInterval(() => setCounter((counter) => counter 1), 100);
100;
}
function pause() {
setIsStarted(false);
clearInterval(myInterval.current);
}
function resetCounter() {
clearInterval(myInterval.current);
setCounter(0);
}
return (
<div>
<p>{counter}</p>
{!isStarted ?
<button onClick={start}>
Start
</button>
:
<button onClick={pause}>
Pause
</button>
}
<button onClick={resetCounter}>Reset</button>
</div>
);
}
\\\
[1]: https://reactjs.org/docs/hooks-reference.html#useref