I'm trying out React Native by implementing a simple timer. When running the code, the counting works perfectly for the first 6 seconds, where then the app starts to act weird.
Here is the code, which you can try in this Expo Snack
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Button } from 'react-native';
const App = () => {
return (
<View style={styles.container}>
<Timer></Timer>
</View>
);
}
const Timer = (props) => {
const [workTime, setWorkTime] = useState({v: new Date()});
const [counter, setCounter] = useState(0);
setInterval(() => {
setCounter( counter 1);
setWorkTime({v:new Date(counter)});
}, 1000);
return (
<View>
<Text>{workTime.v.toISOString()}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
export default App;
CodePudding user response:
For each new component rerender, React create a new interval, which results in a memory leak and unexpected behavior.
Let us create a custom hook for interval
import { useEffect, useLayoutEffect, useRef } from 'react'
function useInterval(callback, delay) {
const savedCallback = useRef(callback)
// Remember the latest callback if it changes.
useLayoutEffect(() => {
savedCallback.current = callback
}, [callback])
// Set up the interval.
useEffect(() => {
// Don't schedule if no delay is specified.
// Note: 0 is a valid value for delay.
if (!delay && delay !== 0) {
return
}
const id = setInterval(() => savedCallback.current(), delay)
return () => clearInterval(id)
}, [delay])
}
export default useInterval
Use it in functional component
const Timer = (props) => {
const [workTime, setWorkTime] = useState({v: new Date()});
const [counter, setCounter] = useState(0);
useInterval(()=>{
setCounter( counter 1);
setWorkTime({v:new Date(counter)});
},1000)
return (
<View>
<Text>{workTime.v.toISOString()}</Text>
</View>
)
}
Here tested result - https://snack.expo.dev/@emmbyiringiro/58cf4b
CodePudding user response:
As @AKX commented, try wrapping your interval code in a useEffect. Also, return a function from it that clears the interval (the cleanup function).
useEffect(() => {
const interval = setInterval(() => {
setCounter( counter 1);
setWorkTime({v:new Date(counter)});
}, 1000);
return () => clearInterval(interval);
});
However, using setInterval with 1000 second delay does not yield an accurate clock, if that's what you're going for.
