I am trying to do a countdown timer but after it gets to 1 it resets to 5 when its supposed to go to '00:00', I don't know where I am going wrong please may someone help me
This is my code:
const CountDown = () => {
const RESET_INTERVAL_S = 5;
const formatTime = (time) =>
`${String(Math.floor(time / 60)).padStart(2, "0")}:${String(
time % 60
).padStart(2, "0")}`;
const Timer = ({ time }) => {
const timeRemain = RESET_INTERVAL_S - (time % RESET_INTERVAL_S);
return (
<>
<Text>{formatTime(timeRemain)}</Text>
</>
);
};
const IntervalTimerFunctional = () => {
const [time, setTime] = useState(0);
console.log("The time is", time);
useEffect(() => {
const timerId = setInterval(() => {
setTime((t) => t + 1);
}, 1000);
return () => clearInterval(timerId);
}, []);
return <Timer time={time} />;
};
return <IntervalTimerFunctional />;
};
I am not sure this is a perfect solution but this could work:
You could stop your timer when it reaches its maximum value:
useEffect(() => {
const timerId = setInterval(() => {
setTime((t) => {
if(t + 1 === RESET_INTERVAL_S) {
clearInterval(timerId)
}
return t + 1;
});
}, 1000);
return () => clearInterval(timerId);
}, []);
And display "00:00" when you have reached the limit:
<Text>{time === RESET_INTERVAL_S ? "00:00" : formatTime(timeRemain)}</Text>
Here is a working example
useEffect(() => {
const timerId = setInterval(() => {
setTime((t) => t + 1);
}, 1000);
return () => clearInterval(timerId);
}, []);
You need to set the value you want to repeat on. If not, this will keep resetting it.
You could also add a condition to check the value of the timer and stop it. like this:
{
time !== 0 ? setTime((t) => t+1): time = 0;
}
Here is a similar problem to yours.
Related
I am facing a problem when the app is running the timer is working correctly however when I reset the timer the countdown timer is stuck in the initial state.
const [timer, setTimer] = useState(30)
useEffect(() => {
let interval = setInterval(() => {
setTimer(prev => {
if (prev === 1) clearInterval(interval)
return prev - 1
})
}, 1000)
newQuestion()
return () => clearInterval(interval)
}, [])
const handleReset = () => {
setTimer(30)
setScore(0)
}
I have a component that sets off a timer which updates and makes an axios request every 30 seconds. It uses a useRef which is set to update every 30 seconds as soon as a function handleStart is fired.
const countRef = useRef(null);
const lastUpdatedRef = useRef(null);
const [lastUpdated, setLastUpdated] = useState(Date.now())
const handleStart = () => {
countRef.current = setInterval(() => {
setTimer((timer) => timer + 1);
}, 1000);
lastUpdatedRef.current = setInterval(() => {
setLastUpdated(Date.now());
}, 30000);
};
Now I have a useEffect that runs a calculate function every 30 seconds whenever lastUpdated is triggered as a dependency:
const firstCalculate = useRef(true);
useEffect(() => {
if (firstCalculate.current) {
firstCalculate.current = false;
return;
}
console.log("calculating");
calculateModel();
}, [lastUpdated]);
This updates the calculate function every 30 seconds (00:30, 01:00, 01:30 etc.) as per lastUpdatedRef. However, I want the timer to restart from when lastUpdated state has been modified elsewhere (e.g. if lastUpdated was modified at 00:08, the next updated will be 00:38, 01:08, 01:38 etc.). Is there a way to do this?
Basically it sounds like you just need another handler to clear and restart the 30 second interval updating the lastUpdated state.
Example:
const handleOther = () => {
clearInterval(lastUpdatedRef.current);
lastUpdatedRef.current = setInterval(() => {
setLastUpdated(Date.now());
}, 30000);
}
Full example:
const calculateModel = () => console.log("calculateModel");
export default function App() {
const countRef = React.useRef(null);
const lastUpdatedRef = React.useRef(null);
const [lastUpdated, setLastUpdated] = React.useState(Date.now());
const [timer, setTimer] = React.useState(0);
const handleStart = () => {
countRef.current = setInterval(() => {
setTimer((timer) => timer + 1);
}, 1000);
lastUpdatedRef.current = setInterval(() => {
setLastUpdated(Date.now());
}, 30000);
};
const handleOther = () => {
clearInterval(lastUpdatedRef.current);
lastUpdatedRef.current = setInterval(() => {
setLastUpdated(Date.now());
}, 30000);
};
const firstCalculate = React.useRef(true);
React.useEffect(() => {
if (firstCalculate.current) {
firstCalculate.current = false;
return;
}
console.log("calculating");
calculateModel();
}, [lastUpdated]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>Timer: {timer}</div>
<button type="button" onClick={handleStart}>
Start
</button>
<button type="button" onClick={handleOther}>
Other
</button>
</div>
);
}
Don't forget to clear any running intervals when the component unmounts!
React.useEffect(() => {
return () => {
clearInterval(countRef.current);
clearInterval(lastUpdatedRef.current);
};
}, []);
I have a stopwatch function in React that I would like to stop after 15 minutes. I am not sure how to use clearInterval() in this case:
const [timer, setTimer] = useState(0);
const [isActive, setIsActive] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const countRef = useRef(null);
const lastUpdatedRef = useRef(null);
const [minutes,setMinutes] = useState(0)
const [seconds,setSeconds] = useState(0)
const timeCeiling = 900; //maximum minutes is 15
const timeFloor = 60; //maximum seconds is 60 so it resets after
useEffect(() => {
if (timer < timeCeiling) {
setMinutes(Math.floor(timer / 60));
setSeconds(timer % 60);
} else {
setMinutes(15);
setSeconds(0);
}
}, [timer]);
const handleStart = () => {
setIsActive(true);
setIsPaused(true);
countRef.current = setInterval(() => {
setTimer((timer) => timer + 1);
}, 1000);
lastUpdatedRef.current = setInterval(() => {
setLastUpdated(Date.now());
}, 30000);
};
The user clicks on the handleStart function which triggers a useEffect. It also has a lastUpdated dependency which triggers another function every 30 seconds.
The clock should end after 15:00 but it still continues after- where should I put clearInterval so that it stops the clock after 15 minutes? Or is there another way to do this?
I would place it in the useEffect that is running each time timer updates. Clear the interval in the else branch when the limit it hit.
useEffect(() => {
if (timer < timeCeiling) {
setMinutes(Math.floor(timer / 60));
setSeconds(timer % 60);
} else {
clearInterval(countRef.current);
setMinutes(15);
setSeconds(0);
}
}, [timer]);
You might also want to add an additional useEffect hook to clear any running timers should the component unmount before you manually clear them.
useEffect(() => {
return () => {
clearInterval(countRef.current);
clearInterval(lastUpdatedRef.current);
};
}, []);
You can add cleare interval in the else condition:
useEffect(() => {
if (timer < timeCeiling) {
setMinutes(Math.floor(timer / 60));
setSeconds(timer % 60);
} else {
setMinutes(15);
setSeconds(0);
countRef.current && clearInterval(countRef.current);
lastUpdatedRef.current && clearInterval(lastUpdatedRef.current);
}
}, [timer]);
And you should cleare interval when component un-mount:
useEffect(() => {
return () => {
countRef.current && clearInterval(countRef.current);
};
}, [countRef]);
useEffect(() => {
return () => {
lastUpdatedRef.current && clearInterval(lastUpdatedRef.current);
};
}, [lastUpdatedRef]);
I think you should use clearInterval in the else block in useEffect. Maybe this way:
else {
setMinutes(15);
setSeconds(0);
clearInterval(countRef.current) // I hope this works
}
I have the following:
const [isPaused, setIsPaused] = useState(false);
const myTimer = useRef(null);
const startTimer = () => {
myTimer.current = setInterval(() => {
console.log(isPaused); // always says "false"
}, 1000);
};
Elsewhere in the code while this timer is running I'm updating the value of isPaused:
setIsPaused(true);
But this isn't reflected in the console log, it always logs false. Is there a fix to this?
The myTimer.current never changed which means isPaused is always false inside the function.
You need to make use of useEffect to update myTimer.current every time isPaused is updated.
useEffect(() => {
function startTimer() {
myTimer.current = setInterval(() => {
console.log(isPaused);
}, 1000);
};
startTimer();
return () => clearInterval(myTimer.current); // cleanup
}, [isPaused]);
You can do something like this,
const [isPaused, setIsPaused] = useState(false);
const myTimer = useRef(null);
const startTimer = () => {
myTimer.current = setInterval(() => {
console.log(isPaused); // now updates
}, 1000);
};
useEffect(() => {
startTimer();
return () => myTimer.current != null && clearInterval(myTimer.current);
}, [isPaused]);
return (
<div>
<b>isPaused: {isPaused ? "T" : "F"}</b>
<button onClick={() => setIsPaused(!isPaused)}>Toggle</button>
</div>
);
Use Others function
use useInterval from 30secondsofcode
const Timer = props => {
const [seconds, setSeconds] = React.useState(0);
useInterval(() => {
setSeconds(seconds + 1);
}, 1000);
return <p>{seconds}</p>;
};
ReactDOM.render(<Timer />, document.getElementById('root'));
Or, use react-useInterval package
function Counter() {
let [count, setCount] = useState(0);
const increaseCount = amount => {
setCount(count + amount);
};
useInterval(increaseCount, 1000, 5);
return <h1>{count}</h1>;
}
I've ResetPassword component which renders Timer component, below are their code -
ResendPassword.js
class ResetPassword extends Component{
constructor(props){
super(props);
this.state = {
resendActive: false
};
}
endHandler(){
this.setState({
resendActive: true
})
}
render(){
return (
<Timer sec={5} counter={this.state.counter} end={this.endHandler.bind(this)}/>
)
}
}
Timer.js
const Timer = (props) => {
const [sec, setSec] = useState(props.sec);
useEffect(() => {
setSec(props.sec);
const intr = setInterval(() => {
setSec((s) => {
if(s > 0)
return --s;
props.end(); // Line: causing warning
clearInterval(intr);
return s;
});
}, 1000)
return () => {
clearInterval(intr);
}
}, [props.counter])
return (
<span>{sec > 60 ? `${Math.floor(sec/60)}:${sec - Math.floor(sec/60)}`: `${sec}`} sec</span>
)
}
In Above code I'm using timer in ResetPassword and I want a function call when timer ends so I'm passing endHandler as end in Timer component but calling that function giving - Warning: Cannot update during an existing state transition (such as within 'render'), can anyone let me know what I'm doing wrong here?
Thanks In Advance
Issue
setSec is a state update function and you use the functional state update variant. This update function callback is necessarily required to be a pure function, i.e. with zero side-effects. The invocation of props.end() is a side-effect.
Solution
Split out the side-effect invocation of props.end into its own effect hook so that it is independent of the state updater function.
const Timer = (props) => {
const [sec, setSec] = useState(props.sec);
useEffect(() => {
setSec(props.sec);
const intr = setInterval(() => {
setSec((s) => {
if (s > 0) return --s;
clearInterval(intr);
return s;
});
}, 1000);
return () => {
clearInterval(intr);
};
}, [props.counter]);
useEffect(() => {
console.log(sec);
if (sec <= 0) props.end(); // <-- move invoking `end` to own effect
}, [sec]);
return (
<span>
{sec > 60
? `${Math.floor(sec / 60)}:${sec - Math.floor(sec / 60)}`
: `${sec}`}{" "}
sec
</span>
);
};
Suggestion
Create a useInterval hook
const useInterval = (callback, delay) => {
const savedCallback = useRef(null);
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
const id = setInterval(savedCallback.current, delay);
return () => clearInterval(id);
}, [delay]);
};
Update Timer to use interval hook
const Timer = ({ end, sec: secProp}) => {
const [sec, setSec] = useState(secProp);
// Only decrement sec if sec !== 0
useInterval(() => setSec((s) => s - (s ? 1 : 0)), 1000);
useEffect(() => {
!sec && end(); // sec === 0, end!
}, [sec, end]);
return (
<span>
{sec > 60
? `${Math.floor(sec / 60)}:${sec - Math.floor(sec / 60)}`
: `${sec}`}{" "}
sec
</span>
);
};