In RN, I have a countdown timer using setInterval that goes from 10 - 0. Once the condition is met of the time === 0 or less than 1, I want the interval to stop. The countdown is working but is repeating continuously, clearInterval not working. What am I doing wrong?
import { StyleSheet, Text } from 'react-native'
import React, {useEffect, useState} from 'react'
export default function Timer() {
const [time, setTime] = useState(10)
useEffect(() => {
if(time > 0) {
var intervalID = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
} else {
clearInterval(intervalID)
}
}, [])
return <Text style={styles.timer}>{time}</Text>
}
CodePudding user response:
The function is on useEffect that only get executed when the component is mounted/unmounted, at the moment where the function is executed, time is 10 so will never go into else condition.
This code must work for you:
import { StyleSheet, Text } from 'react-native'
import React, {useEffect, useState} from 'react'
export default function Timer() {
const [time, setTime] = useState(10)
time <= 0 && clearInterval(intervalID)
useEffect(() => {
var intervalID = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
return () => {
clearInterval(intervalID)
}
}, [])
return <Text style={styles.timer}>{time}</Text>
CodePudding user response:
ClearInterval should be inside, setTime because useEffect will only trigger once.
NOTE: you should also clear on unmount.
export default function Timer() {
const [time, setTime] = useState(10);
useEffect(() => {
var intervalID = setInterval(() => {
setTime((time) => {
if (time > 0) {
return time - 1;
}
clearInterval(intervalID);
return time;
});
}, 1000);
return () => clearInterval(intervalID);
}, []);
return <Text style={styles.timer}>{time}</Text>;
}
CodePudding user response:
You can use this also:
import React, { useEffect, useState, useRef } from 'react'
import { Text } from 'react-native'
const Timer = () => {
const [time, setTime] = useState(10)
const promiseRef = useRef(null)
useEffect(() => {
if(time > 0) {
promiseRef.current = setInterval(() => {
setTime(time => time > 0 ? time - 1 : time = 10)
}, 1000)
} else {
promiseRef.current = null
}
}, [])
return <Text style={styles.timer}>{time}</Text>
}
export default Timer
CodePudding user response:
Your useEffect does not have any value(time) inside the dependency array, so it will run only once on component mount.
const [time, setTime] = useState(10);
useEffect(() => {
if (time === 0) {
return;
}
const timeoutId = setInterval(() => {
setTime(time - 1);
}, 1000);
return () => {
clearInterval(timeoutId);
};
}, [time]);
You could also use timeout, where you don't need to clear anything as it will run only once per useEffect trigger.
useEffect(() => {
if (time === 0) {
return;
}
setTimeout(() => {
setTime(time - 1);
}, 1000);
}, [time]);
Another option that might work if you don't want to add time
to the dependency array is to clear the interval inside the setTime
useEffect(() => {
const intervalID = setInterval(() => {
setTime((time) => {
if (time === 0) {
clearInterval(intervalID);
return time;
}
return time - 1;
});
}, 1000);
return () => {
clearInterval(intervalID);
};
}, []);
Note that in your example you are counting down to 1 and the going back to 10, so it will never reach 0
setTime(time => time > 0 ? time - 1 : time = 10)
if you want it to reset back to 10 after it finishes count the last option might work for you, as it won't trigger the effect on every time change, just need to return 10
instead of time
after clearing the interval
Here is an example https://jsfiddle.net/dnyrm68t/