How to prevent setInterval from clearing the counter - react-native

I'm struggling in using setInterval on react native with hooks;
I've seen some materials about it online and I'm using this custom hook which uses the state counter to display the current element from the array, overtime the counter is increased but it goes blank after it's done with the life cycle of setInterval;
How can I set it to leave it at the latest value or resetting to the first once it's done?
Also the reset button bugs sometimes, it tries to reset, but then come back stopped at the previous position, Am I doing something wrong?
My code so far:
const SensorsDetail = ({ evaluation }) => {
const [ state ] = useState(evaluation);
const [count, setCount] = useState(0)
const [running, setRunning] = useState(false)
const cb = useRef()
const id = useRef()
const start = () => setRunning(true)
const pause = () => setRunning(false)
const reset = () => {
setRunning(false)
setCount(0)
}
function callback () {
setCount(count + 1)
}
// Save the current callback to add right number to the count, every render
useEffect(() => {
cb.current = callback
})
useEffect(() => {
// This function will call the cb.current, that was load in the effect before. and will always refer to the correct callback function with the current count value.
function tick() {
cb.current()
}
if (running && !id.current) {
id.current = setInterval(tick, 250)
}
if (!running && id.current) {
clearInterval(id.current)
id.current = null
}
return () => id.current && clearInterval(id.current)
}, [running])
return(
<View style={styles.view}>
<Card>
<Text style={styles.text}>{state.dados_sensor_1[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_2[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_3[count]}</Text>
</Card>
<Card>
<Text style={styles.text}>{state.dados_sensor_4[count]}</Text>
</Card>
<TouchableOpacity onPress={start} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Start
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Pause
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={reset} style={styles.buttonStyle}>
<Text style={styles.textStyle2}>
Reset
</Text>
</TouchableOpacity>
</View>
);
};

This is silly but after studying more about it, I saw that actually what was happening was that the counter was moving past the array size, that's why it was showing blank values;
I just add a limit to value the counter could raise and it's working just fine;
The reset though is still bugging sometimes...
this is the code for the custom hook:
import { useState, useEffect, useRef } from 'react';
export default (initialCount, finalCount, autoStart) => {
const [count, setCount] = useState(initialCount)
const [running, setRunning] = useState(autoStart)
const cb = useRef()
const id = useRef()
const start = () => {
if (count < finalCount){
setRunning(true)
}
}
const pause = () => setRunning(false)
const reset = () => {
setRunning(false);
setCount(initialCount)
}
function callback () {
setCount(count + 1)
if (count == finalCount){
setRunning(false)
}
}
useEffect(() => {
cb.current = callback
})
useEffect(() => {
function tick() {
cb.current()
}
if (running && !id.current) {
id.current = setInterval(tick, 250)
}
if (!running && id.current) {
clearInterval(id.current)
id.current = null
}
return () => id.current && clearInterval(id.current)
}, [running])
return {
count,
start,
pause,
reset
}
};

Related

How to use asyncStorage inside useEffect

I'm building a mobile game using react native and I'm trying to retrieve the best value storage on it to display on the screen. The problem is that it seems that react native is rendering the screen before it retrieves the value and then it doesn't re-render when the value is updated using setBest(), so no value is displayed.
Here is the code:
const navigation = useNavigation()
const [result, setResult] = useState('')
const [best, setBest] = useState('')
useEffect(() => {
const Storage = async (key,value) => {
await AsyncStorage.setItem(key,value)
}
const Retrieve = async (key) => {
const value = await AsyncStorage.getItem(key)
setBest(()=>value)
}
Retrieve('1').catch(console.error)
setResult(route.params.paramKey)
if(route.params.paramKey>best){
var aux = result.toString()
Storage('1',aux)
console.log(best)
}
}, [])
return (
<View style={styles.container}>
<View style={styles.textView}>
<Text style={styles.tituloText}>Melhor pontuação</Text>
<Text style={styles.tituloText}>{best}</Text>
<Text style={styles.tituloText}>Sua pontuação</Text>
<Text style={styles.resultText}>{result}</Text>
<View style={styles.viewBtn}>
<TouchableOpacity style={styles.viewBack} onPress={() => navigation.navigate('Modo1')}>
<Icon style={styles.iconBack} name="backward" />
</TouchableOpacity>
<TouchableOpacity style={styles.viewHome} onPress={() => navigation.dispatch(StackActions.popToTop)}>
<Icon style={styles.iconBack} name="home" />
</TouchableOpacity>
</View>
</View>
</View>
);
}
Thanks for the help guys! I've been struggling with this for days and any help will be appreciated!
This is how you retrieve the value..
useEffect(() => {
AsyncStorage.getItem('key').then(value => {
if (value != null) {
console.log(value);
setBest(value);
}
});
}, []);
also don't forget to add the import statement..
To set the value you must use
AsyncStorage.setItem('key', value);
You can use Async Functions inside of ~useEffect()` like this:
useEffect(() => {
(async () => {
async function getData() {
try {
const value = await AsyncStorage.getItem('myKey');
if (value !== null) {
setData(value);
}
} catch (error) {
console.log(error);
}
}
getData();
})();
}, []);
}

Using socket.io and useEffect causing delays the more I add to an array?

The code below works but if I click the button more and more there the app freezes up and the update occurs after a delay. That delay is increased significantly after each button press.
How can I avoid this delay/freeze up?
const ContactChatScreen = ({navigation}) => {
const mySocket = useContext(SocketContext);
const [chatMsgs, setChatMsgs] = useState([]);
const sendMsg = () => {
mySocket.emit('chat msg', 'test');
};
useEffect(() => {
mySocket.on('chat msg', (msg) => {
setChatMsgs([...chatMsgs, msg]);
});
});
return (
<View style={styles.container}>
<StatusBar
backgroundColor={Constants.SECONDN_BACKGROUNDCOLOR}
barStyle="light-content"
/>
{chatMsgs.length > 0
? chatMsgs.map((e, k) => <Text key={k}>{e}</Text>)
: null}
<Text style={styles.text}>Contact Chat Screen</Text>
<MyButton title="test" onPress={() => sendMsg()} />
</View>
);
I was able to fix this by changing this:
useEffect(() => {
mySocket.on('chat msg', (msg) => {
setChatMsgs([...chatMsgs, msg]);
});
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
mySocket.off('chat msg');
};
}, [chatMsgs]);
adding the mySocket.off was the key.

Calling function in main file from component

I have recently refactored my app from using Class components to Functional components and having issues with a few last things.
My Home.js looks like the following (simplified):
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
start = () => {
console.log("START");
// stuff
};
reset = () => {
console.log("RESET");
// stuff
};
stop = () => {
console.log("STOP");
// stuff
};
return (
<View style={styles.container}>
<StartStopButtons
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
My StartStopButtons has a different look, depending of the current state, it will either display Start, Stop or Reset and call the corresponding function. I am currently putting this intelligence in another file, my Button.js file.
Button.js :
//imports....
export const StartStopButtons = ({
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};
Before the refactoring, I was using this.state.start, this.state.stop to call my start and stop functions, located in Home.js.
How can I achieve that now? Is there a better approach?
You can pass the functions as props exactly like how you pass isRunning, isReset, and elapsedMilliseconds.
But please add const before function names as well.
// imports....
import { StartStopButtons } from "../components/Button";
export default ({ navigation }) => {
const [scrollEnabled, setScrollEnabled] = useState(false);
const [elapsedMilliseconds, setElapsedMilliseconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const [startTime, setStartTime] = useState(false);
const [stopTime, setStopTime] = useState(false);
const [isReset, setIsReset] = useState(true);
const start = () => {
console.log("START");
// stuff
};
const reset = () => {
console.log("RESET");
// stuff
};
const stop = () => {
console.log("STOP");
// stuff
};
const pause = () => {};
return (
<View style={styles.container}>
<StartStopButtons
start={start}
stop={stop}
reset={reset}
pause={pause}
isRunning={isRunning}
isReset={isReset}
elapsedMilliseconds={elapsedMilliseconds}
/>
</View>
);
};
and use them like
//imports....
export const StartStopButtons = ({
start,
stop,
reset,
pause,
isRunning,
isReset,
elapsedMilliseconds,
}) => {
if (isRunning && isReset === false) {
return (
<View>
<TouchableOpacity onPress={stop}>
<Text>Stop</Text>
</TouchableOpacity>
<TouchableOpacity onPress={pause}>
<Text>Pause</Text>
</TouchableOpacity>
</View>
);
} else {
if (elapsedMilliseconds === 0) {
return (
<TouchableOpacity onPress={start}>
<Text>Start</Text>
</TouchableOpacity>
);
} else {
return (
<TouchableOpacity onPress={reset}>
<Text>Reset</Text>
</TouchableOpacity>
);
}
}
};

react native onpressin and onpressout

I tried to create a long press button to keep counting, however the interval wont stop when I release the onPressOut
const App = () => {
const [count, setCount] = useState(0);
let timer;
const onButtonPressIn = () => {
timer = setInterval(() => {
console.log("in");
setCount(count => count + 1);
}, 1000);
}
const onButtonPressOut = () =>{
console.log("out");
clearInterval(timer);
}
return(
<View>
<TouchableOpacity style={styles.longbtn}
activeOpacity={0.9}
onPressIn={()=> onButtonPressIn()}
onPressOut={()=> onButtonPressOut()}
>
<Text style={styles.longbtntt}>LONG PRESS TO START - {count}</Text>
</TouchableOpacity>
</View>
)
}
The method used to handle the timer is not the exact one for the sort of use case you are trying to implement it for. You have to use clearTimeout instead of clearInterval when you're dismissing the Timer . Solution is as below, just modify it to use hooks.
import React from 'react';
import {
SafeAreaView,
View,
Dimensions,
TouchableOpacity,
Text
} from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
this.timer = null;
}
addOne = () => {
this.setState({counter: this.state.counter+1});
this.timer = setTimeout(this.addOne, 200);
}
stopTimer = () => {
clearTimeout(this.timer);
}
render() {
const screenHeight = Math.round(Dimensions.get('window').height);
return (
<SafeAreaView>
<View style={{height: screenHeight}}>
<TouchableOpacity
style={{backgroundColor: "#FFC0CB"}}
onPressIn={this.addOne}
onPressOut={this.stopTimer}
>
<Text>Press to start</Text>
</TouchableOpacity>
<Text>{this.state.counter}</Text>
</View>
</SafeAreaView>
);
}
}
The timer changes value when you assign a new interval to it, but the function onButtonPressOut remembers the value that timer had when the function was created. You can use useCallback here, like so:
const onButtonPressOut = useCallback(() => { //same thing }, [timer]);
This will update the function whenever timer changes and clear the appropriate interval.

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

I want to login to my app but when I first login it works correctly but once I logout from my app and again try to login I get the following error 'Can't perform a state update on an unmount component'. Even though second time it also enters in the app but with the error which should be not there. Only one time it works correctly.
/*Component*/
const LoginScreen = props => {
let _isMounted = false;
const [isLoading , setIsLoading] = useState(false);
const [error , setError] = useState();
const [token , setToken] = useState();
const [url , setUrl] = useState({});
const dispatch = useDispatch();
/*Receiving the token*/
useEffect(() => {
let _isMounted = false;
const tokenReceive = () => {
if(Object.entries(url).length !== 0)
{
const getTokenFromUrl = url['url'].split('=')[1].split('&')[0];
if(getTokenFromUrl !== '')
{
setToken(getTokenFromUrl)
}
}
}
tokenReceive();
return(() => {
_isMounted = true
} )
}, [url ])
/*Dispatching after receiving token*/
useEffect(() =>{
_isMounted = true;
const loginHandler = async ()=> {
if(token !== undefined)
{
setError(null)
setIsLoading(true);
try{
await dispatch(authActions.login(token))
// if(_isMounted){
// props.navigation.navigate('afterAuth')
// }
}
catch(err)
{
setError(err.message)
}
setIsLoading(false)
if(_isMounted){
props.navigation.navigate('afterAuth')
}
}
}
loginHandler()
return(() => {
_isMounted = false
} )
} , [token ])
/*If any error occur*/
useEffect(() => {
if (error) {
Alert.alert('An error occured',error,[{text : 'Okay'}]);
}
return(() => {
console.log('Error'),
error
})
} , [error])
/*Event listener when url changes*/
useEffect(() => {
Expo.Linking.addEventListener('url', (url) => {
setUrl(url);
})
return () => {
Expo.Linking.removeEventListener('url' , (url) => {
setUrl(url)
})
};
} , [])
const prefix = Expo.Linking.makeUrl('token');
const _handlePressButtonAsync = async () => {
let result = await WebBrowser.openBrowserAsync(`https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=**********&response_type=id_token&redirect_uri=${prefix}&scope=openid email profile&response_mode=fragment&state=*****&nonce=****`);
};
return(
<ScrollView >
<TouchableWithoutFeedback onPress={() => {Keyboard.dismiss()}} >
<View style={styles.screen}>
<CircleDiv style={styles.userlogoDiv}>
<View style={styles.userLogo}>
<AntDesign name="user" size={RFValue(39)} color='#4D4848'/>
</View>
</CircleDiv>
<BackgroundUpper style={styles.upperDiv}>
<LogoLong style={ {marginTop : RFValue(100)}}/>
</BackgroundUpper>
<BackgroundLower >
<ScrollView style={{ flex : 1 } } decelerationRate='fast' >
<KeyboardAvoidingView behavior='position' keyboardVerticalOffset={Dimensions.get('screen').height / RFValue(10)}>
<View style={styles.loginDiv}>
<View style={styles.headingDiv}>
<Text style={styles.heading}>LOGIN</Text>
</View>
<View style={styles.buttonDiv}>
<TouchableOpacity>
{!isLoading ? <Button
style={styles.button}
title='LOGIN'
color= '#00B49D'
//onPress = {navigate}
onPress={_handlePressButtonAsync}
/> : <ActivityIndicator size="small" color={Colors.GREEN}/>}
</TouchableOpacity>
</View>
<View style={styles.forgetDiv}>
<Text style={styles.forget}>Forget Password</Text>
</View>
</View>
</KeyboardAvoidingView>
</ScrollView>
</BackgroundLower>
</View>
</TouchableWithoutFeedback>
</ScrollView>
)
};
Error - Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function,