Solution
Fix the useEffect bug
The bug is evident whenever we reset the timer. But it also can be noticed when the timer is left to run for a few seconds.
The seconds state variable is set inside a setInterval which itself is inside a useEffect.
The current useEffect runs every time the seconds state variable changes and the setInterval is not being cleared resulting in a memory leak.
We can fix this bug with a simple useEffect cleanup function.
React.useEffect(() => {
const interval = setInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, [seconds]);Did you know that instead of clearInterval you can also use clearTimeout? Both of them share the same pool of IDs. But for readability, we should use the respective clear functions.
Though the bug is fixed, we prefer a different way to set the state which allows us to not pass the seconds value to the useEffect dependency array. setSeconds can take an updater function
React.useEffect(() => {
const interval = setInterval(() => {
setSeconds(seconds + 1);
setSeconds(seconds => seconds + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, [seconds]);
}, []);Bonus
Creating a useTimer hook is a great way to abstract away the timer logic.
import React from 'react';
export default function useTimer() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
const resetTimer = () => {
setSeconds(0);
};
return { seconds, resetTimer };
}All we had to do was extract the useState and useEffect logic into the useTimer hook. The useTimer hook returns an object containing the seconds state and a resetTimer function which we use back in the App component.
import useTimer from './useTimer';
export default function App() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => {
setSeconds((seconds) => seconds + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
const { seconds, resetTimer } = useTimer();
return (
<div className="p-4 text-center">
<p>Timer: {seconds} Seconds</p>
<button
className="rounded bg-red-500 p-2 hover:bg-red-600"
onClick={() => setSeconds(0)}
onClick={resetTimer}
>
Reset Timer
</button>
</div>
);
}GreatFrontEnd has better questions!
We don't create questions because GreatFrontEnd already has the best possible library of questions and we recommend the same to our community.
Last updated on