How can i refresh data with setInterval when Actions.pop()? - react-native

I'm trying to create live dashboard mobile app with react-native. I setInterval to fetch data every 5 sec. When i go to other actions i clearIntervar(cause if i don't clear it continues other pages) and it's ok but when i try to Action.pop() i cant setInterval again.
I tried to setInterval in componentWillUnmount() and Action.refresh(with same props) but every time; i get the same error.
Warning: 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 the
componentWillUnmount method.
This is the sample like my code:
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
}
componentDidMount() {
this.getData()
}
async getData() {
//just a sample
const data = await fetch(url).then((response) => response.json());
this.setState({data: data});
if (this.state.isRefresh) {
const intervalId = setInterval(() => {
this.getData();
}, 5000);
this.setState({
intervalId: intervalId,
isRefresh: true
})
}
}
render() {
return (
<View>
<Text>{this.state.data}</Text>
<Button onPress={() => {
clearInterval(this.state.intervalId);
Action.otherPage();
}
} title={'Test Button'}/>
</View>
)
}
}
I have to setInterval and fetch data in the other pages too. So i need to clear when i go to other pages and need to setInterval when i come back with Actions.pop()

Don't store intervalId in state, instead you should make use of instance variable for your interval,
constructor(props) {
super(props);
this.state = {
isRefresh: false
}
this.intervalId = null; //instance variable
}
Then assign your interval to instance variable,
this.intervalId = setInterval(() => { this.getData();}, 5000);
Then use componentWillUnmount to clear interval,
componentWillUnmount(){
clearInterval(this.intervalId);
}

Please use this
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action you want when a user on this screen
});
}
componentWillUnmount() {
// Remove the event listener
this.focusListener.remove();
}

Related

addlistener focus react native only works on second refresh react-native

so I've been trying to reload the content from asyncStorage in a screen when navigating back from a second screen, but it only refreshes when i navigate forth and back again
here is my code
componentDidMount() {
const {navigation} = this.props
navigation.addListener('focus', () => {
AsyncStorage.getItem('Servers').then((servers) => {
servers = JSON.parse(servers);
if (servers) {
return this.setState({servers:servers, loaded: true})
}
this.setState({servers: [], loaded: true});
});
});
};
Also, i think it should be re-rendering everytime a setState is done, but its not doing it for some reason
this is my code after the changes:
focusHandler(){
AsyncStorage.getItem('Servers').then((servers) => {
servers = JSON.parse(servers);
if (servers.length) {
return this.setState({servers, carregado: true})
}
this.setState({carregado: true});
});
}
componentDidMount() {
const {navigation} = this.props
this.focusHandler();
navigation.addListener('focus', this.focusHandler());
};
it gives the following error:
That's the expected behavior ... cause you've only registered a listener for focus event .... Execute the callback of addListener directly in componentDidMount...
componentDidMount() {
const {navigation} = this.props;
yourFocusHandler();
this.unsubscribe = navigation.addListener('focus', yourFocusHandler);
};
componentWillUnmount() {
this.unsubscribe();
}

React Native : Conditional render() based on AsyncStorage result

Trying to use a AsyncStorage variable to conditionally render content.
My app uses createBottomTabNavigator from react-navigation. I have a tab called Settings that must conditionally render content based on wether a user is logged in or not (checking AsyncStorage). The following code works on first render but another tab can update AsyncStorage value, returning back to Settings tab it still renders initial content.
Which approach can i use to achieve this, i'm also trying to use shouldComponentUpdate but i'm not sure how it works.
import React, {Component} from 'react';
class Settings extends React.Component{
constructor(props){
super(props);
this.state = {
isLoggedIn:false
};
}
//I want to use this method but not sure how.
shouldComponentUpdate(nextProps, nextState){
// return this.state.isLoggedIn != nextState;
}
componentDidMount(){
console.log("componentWillUpdate..");
this.getLocalStorage();
}
getLocalStorage = async () => {
try {
const value = await AsyncStorage.getItem('username');
if(value !== null) {
this.setState({isLoggedIn:true});
}
} catch(e) {
// error reading value
}
}
render() {
if(this.state.isLoggedIn)
{
return(
<View>
<Text style={styles.title_header}>Logged In</Text>
</View>
);
}
else{
return(
<View>
<Text style={styles.title_header}>Logged Out</Text>
</View>
);
}
}
}
export default Settings;
})
Use NavigationEvents. Add event listeners to your Settings components.
onWillFocus - event listener
onDidFocus - event listener
onWillBlur - event listener
onDidBlur - event listener
for example, the following will get fired when the next screen is focused.
focusSubscription = null;
onWillFocus = payload => {
// get values from storage here
};
componentDidMount = () => {
this.focusSubscription = this.props.navigation.addListener(
'willFocus',
this.onWillFocus
);
};
componentWillUnmount = () => {
this.focusSubscription && this.focusSubscription.remove();
this.focusSubscription = null;
};
The problem comes from react-navigation createBottomTabNavigator. On first visit, the component is mounted and so componentDidMount is called and everything is great.
However, when you switch tab, the component is not unmounted, which means that when you come back to the tab there won't be any new call to componentDidMount.
What you should do is add a listener to the willFocus event to know when the user switches back to the tab.
componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', () => {
AsyncStorage.getItem('username').then((value) => {
if (value !== null) {
this.setState({ isLoggedIn: true });
}
catch(e) {
// error reading value
}
});
});
}
Don't forget to remove the listener when the component is unmounted:
componentWillUnmount() {
this.listener.remove();
}

Undefined props only in componentDidMount

In my code below you can see my component. How it is written will cause the app to crash with the error:
undefined is not an object (evaluation this.props.data.ID)
So in my componentDidMount that id variable is not receiving the props data.
However if i comment out that code in the componentDidMount the app will load fine and the props.data.ID will print out in View. Is there a reason why i can't access the props.data.ID in my componentDidMount?
Heres my code
// timeline.js
class TimelineScreen extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
}
}
componentDidMount() {
const { id } = this.props.data.ID;
axios.post('/api/hometimeline', { id })
.then(res => {
this.setState({
posts: res.data
});
});
}
render() {
const { data } = this.props;
return (
<View style={s.container}>
{
data
?
<Text>{data.ID}</Text>
:
null
}
</View>
);
}
}
function mapStateToProps(state) {
const { data } = state.user;
return {
data
}
}
const connectedTimelineScreen = connect(mapStateToProps)(TimelineScreen);
export default connectedTimelineScreen;
The input of mapStateToProps is not react state, it is redux store. You shouldn't use this.setState in componentDidMount. Use redux actions and reducers to change redux store. Whenever redux store changes, it will invoke mapStateToProps and update your props
componentDidMount() {
console.log(this.props.data); // for test
const id = this.props.data.ID;
//OR
const {id} = this.props.data;
...
}

react-native-camera barcode scanner freezes, because it scans too fast

I am trying to use the barcode scanner from react-native-camera. First, off it scans a QR-code and extracts a String, after that it navigates to the next Screen with react-navigation. In the second screen, it makes an API-call.
Now if I go back to the scanner screen, de QR-code will be scanned immediately. That's where I run into an error and the scanner freezes. I usually get this error:
Can't call setState (or forceUpdate) on an unmounted component
I think it's because my componentWillUnmount cleanup doesn't work properly or fast enough, but I already cancel the axios request.
requestCode = (code) => {
if (cancel != undefined) {
cancel();
}
axios.get(API_URI + code, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).then(response => {
console.log(response)
//checks if code was already called
this.checkUsed(response.data)
})
.catch(error => {
this.setState({ isValid: false })
});
}
componentWillUnmount() {
cancel();
}
Maybe I could mount the camera-scanner a little bit later so it doesn't scan this fast or is it maybe even an error with React Navigation?
You can use a flag to control.
class QR extends Component {
constructor(props) {
super(props)
this.state = {
scanable: true
}
this.cameraAttrs = {
ref: ref => {
this.camera = ref
},
style: styles.preview,
type: RNCamera.Constants.Type.back,
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
onBarCodeRead: ({ data }) => {
this.callback(data)
}
}
}
componentWillMount() {
this._mounted = true
}
componentWillUnmount() {
this._mounted = false
}
callback(text) {
if (!this.state.scanable) {
return
}
console.log(text)
this.setState({ scanable: false })
setTimeout(() => {
if (this._mounted) {
this.setState({ scanable: true })
}
}, 1000) // 1s cooldown
}
render() {
return (
<View style={styles.container}>
<RNCamera
{...this.cameraAttrs}
>
</RNCamera>
</View>
)
}
}

React Native setInterval doubles it self after restarting app

All the time setInterval acts as I am expecting except for one instance. If user leaves the app by clicking 'home button' and restarts app by clicking the icon (not by selecting from overview), the old setInterval haven't stopped. It starts over again, and there are now running 2 setIntervals. It goes on and on if I repeat this process and user can end up running hundreds of intervals at the same time.
How do I prevent this? I want to run only one instance of seInterval at the time.
Here's my code:
import React, { Component } from 'react';
import {
AsyncStorage,
Text,
View,
AppState,
} from 'react-native';
import timer from'react-native-timer';
class Code_temp extends Component {
redirect(routeName){
this.props.navigator.push({
name: routeName
});
}
constructor(props){
super(props);
this.state = {
appState: AppState.currentState,
};
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
this.setState({showMsg: true}, () => timer.setInterval(
this, 'hideMsg', () =>{
console.log( 1 );
}, 1000
));
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
}
this.setState({appState: nextAppState});
};
componentWillMount() {
}
render() {
return (
<View>
<Text>
Test
</Text>
</View>
);
}
}
export default Code_temp
That's because you are not clearing the setInterval when the component is unmounted, also remove 'this' from setinterval arguments
componentDidMount() {
timer.clearInterval('hideMsg');
AppState.addEventListener('change', this._handleAppStateChange);
this.setState({showMsg: true}, () => timer.setInterval(
'hideMsg', () =>{
console.log( 1 );
}, 1000
));
}
componentWillUnmount() {
timer.clearInterval('hideMsg');
};