I am trying to build a timer with three buttons, a start, stop, where it stops on the current integer, and a reset. I have added my code below which results in the following issues.
I thought my stop function would stop the timer from decrementing but it continues to do so. Also, when logging my timer state in the console, you can see it does not update in the console even though it is updating in the DOM. Why is this?
Thank you for any insight at all.
import React from 'react';
import './style.css';
export default function App() {
const [timer, setTimer] = React.useState(50);
const reset = () => {
setTimer(50);
};
const start = () => {
setTimer((prev) => prev - 1);
};
// const interval = setInterval(() => {
// console.log(updated)
// //start() }, 1000)
// }
const interval = () => {
setInterval(() => {
console.log('updated');
console.log(timer);
start();
}, 1000);
};
const stop = () => {
clearInterval(start);
};
return (
<div>
<h1>{timer}</h1>
<button onClick={interval}>start</button>
<button onClick={stop}>stop</button>
<button onClick={reset}>reset</button>
</div>
);
}`
CodePudding user response:
You have a small problem with assigning the actual value for the interval.
Here is how it should be in usage
const interval = setInterval(() => {})
clearInterval(interval)
For your code change, you can create a ref to keep the interval variable and use it to clean up the interval later.
function App() {
const [timer, setTimer] = React.useState(5);
const intervalRef = React.useRef(); //create a ref for interval
const reset = () => {
setTimer(5);
};
const start = () => {
setTimer((prev) => {
if(prev === 0) {
stop();
return 0;
}
return prev - 1;
});
};
// const interval = setInterval(() => {
// console.log(updated)
// //start() }, 1000)
// }
const interval = () => {
//assign interval ref here
intervalRef.current = setInterval(() => {
start();
}, 1000);
};
const stop = () => {
//clear the interval ref
clearInterval(intervalRef.current);
};
return (
<div>
<h1>{timer}</h1>
<button onClick={interval}>start</button>
<button onClick={stop}>stop</button>
<button onClick={reset}>reset</button>
</div>
);
}
ReactDOM.render(
<App/>,
document.getElementById("root")
);
<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>
<div id="root"></div>
CodePudding user response:
clearTimeout
or clearInterval
each take a token, which was returned by a previous call to setTimeout
or setInterval
. So you'll need to store that token:
const id = setInterval(() => console.log("triggered"), 1000);
// ...
clearInterval(id)
Also, you should be careful about what happens if App
is re-rendered, so you should probably put the set
/clear
logic inside useEffect
so you can cleanup your interval.
Also also, although you didn't ask, your console.log(timer)
isn't going to work, and will always print 50. The timer
variable inside that callback is captured once, and is never updated because that callback is just inside the setInterval
now. You'll need to clear and reset your interval with an updated callback function every time App
re-renders, or use a ref that you keep updated, which is a pain.
I would recommend borrowing this custom hook that considers all of these things for you: https://usehooks-ts.com/react-hook/use-interval
Then your App
component could become extremely simple, but still be robust:
const { useEffect, useRef, useState, useLayoutEffect } = React;
// https://usehooks-ts.com/react-hook/use-interval
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useLayoutEffect(() => {
savedCallback.current = callback;
}, [callback])
useEffect(() => {
if (!delay && delay !== 0) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [timer, setTimer] = useState(50);
const [running, setRunning] = useState(false);
useInterval(() => setTimer(t => t - 1), running ? 1000 : null);
const start = () => setRunning(true);
const stop = () => setRunning(false);
const reset = () => { setTimer(50); };
return (
<div>
<h1>{timer}</h1><button onClick={start}>start</button>
<button onClick={stop}> stop </button>
<button onClick={reset}> reset </button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
CodePudding user response:
I think you should not wrap your setTimeout into a callable. Then you lose the ability to start and stop it because the variable does not reference the interval but the callable that wraps the interval.
Take a look at this guide: https://www.w3schools.com/jsref/met_win_clearinterval.asp