When update the default time with setInterval
, it’s not working as expected. Instead it’s added as a new instance. How to clear the setInterval
in the custom hook and update new value?
app.jsx
import React from 'react';
import './style.css';
import CustomTimer from './Custom';
import { useState, useEffect } from 'react';
export default function App() {
const [intervalTime, setIntervalTime] = useState(200);
const time = CustomTimer(intervalTime);
useEffect(() => {
setTimeout(() => {
console.log('Hi');
setIntervalTime(500);
}, 5000);
});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some happen! {time} </h2>
</div>
);
}
Custom.js
import { useEffect, useState } from 'react';
function CustomTimer(startTime) {
const [timer, setTimer] = useState(startTime);
useEffect(() => {
const myInterval = setInterval(() => {
if (timer > 0) {
setTimer(timer - 1);
console.log(timer);
}
}, 1000);
return () => clearInterval(myInterval);
}, [startTime]);
return timer;
}
export default CustomTimer;
Live Demo => please check the console
2
Answers
Issues
This is the classic stale closure of state in a callback problem, with some additional discrepancies.
timer
in thesetInterval
callback is the same value from the initial render cycle, so it always decrements from the same value.useEffect
hook inCustomTimer
doesn’t "reset" thetimer
state when thestartTime
prop value is changed.useEffect
hook inApp
is missing a dependency array so it starts a new timeout each render cycle afterintervalTime
is updated the first time.CustomTimer
is really a custom React hook, it should be correctly renamed touseCustomTimer
.Solution
I suggest using a React ref to hold a reference to the current
timer
state value, to be used in thesetInterval
callback. This is to check if the timer is still valid. Use a functional state update to enqueuetimer
state updates.Example:
Demo
The results can be tested here as well ==> Test Demo
First of all, need to add a dependency to the
useEffect
in theApp.js
file since whenever it triggers, the timer starts from the beginning.App.js:
Custom.js: (Added comment lines to explain the added/updated lines)