Reputation: 151
I am getting the following error message when I try to transition from one screen to another:
This is happening in a game app where multiple phones are involved in the game and have different screens depending on their role in the game and depending if that phone is hosting the game or is a guest.
The following image shows this error message when I am trying to reach the next screen (on the left phone). The screen on the left phone is supposed to be the same as the one on the right, but without the buttons "Next Round" and "End Game". But I am getting a totally different screen with the error message:
Here is the code for the previous screen that is supposed to navigate the phones to this "score" screen:
import React, {Component} from 'react';
import {AppRegistry, View, Text, ScrollView, StyleSheet} from 'react-native';
import {CardFlip1} from '../components/CardFlip1';
import {CardFlip2} from '../components/CardFlip2';
import colors from '../config/colors';
import {PrimaryButton} from '../components/PrimaryButton';
import AsyncStorage from '@react-native-community/async-storage';
import Orientation from 'react-native-orientation-locker';
window.navigator.userAgent = 'react-native';
import io from 'socket.io-client/dist/socket.io';
class judge_screen extends Component {
constructor (props) {
super(props);
this.state = {
game_round: '',
player1: '',
player2: '',
label1: '',
label2: '',
current_user: ''
}
}
componentWillMount = () => {
this.getActives();
}
componentDidMount() {
Orientation.lockToLandscape();
this.socket = io("socket address is here", {
jsonp: false
});
}
getActives = async () => {
let user = await AsyncStorage.getItem('email');
let player_1 = await AsyncStorage.getItem('Player1');
let player_2 = await AsyncStorage.getItem('Player2');
let round = await AsyncStorage.getItem('Round');
this.setState({game_round: round});
this.setState({player1: player_1});
this.setState({player2: player_2});
var label_start = "Choose ";
var label_end = "'s fate";
var player_1_name = this.state.player1;
var player_2_name = this.state.player2;
var label1_str = label_start.concat(player_1_name, label_end);
this.setState({label1: label1_str});
var label2_str = label_start.concat(player_2_name, label_end);
this.setState({label2: label2_str});
}
player1Win = async () => {
let host = await AsyncStorage.getItem('host');
if (host == 'yes') {
let user = await AsyncStorage.getItem('email');
this.setState({current_user: user});
} else {
let user = await AsyncStorage.getItem('users_id');
this.setState({current_user: user});
}
var user_fix = this.state.current_user;
let player_name = await AsyncStorage.getItem('Player1');
AsyncStorage.setItem('Winner', player_name);
fetch('fetch address is here', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: user_fix,
Name: player_name,
Host: host
})
}).then((response) => response.json())
.then((responseJson) => {
if (host == 'yes') {
this.socket.emit('end_round', 'end');
this.props.navigation.navigate('end_round_host_screen');
} else {
// This is the navigation to the screen getting the error:
this.socket.emit('end_round', 'end');
this.props.navigation.navigate('end_round_guest_screen');
}
}).catch((error) => {
console.error(error);
});
}
player2Win = async () => {
let host = await AsyncStorage.getItem('host');
if (host == 'yes') {
let user = await AsyncStorage.getItem('email');
this.setState({current_user: user});
} else {
let user = await AsyncStorage.getItem('users_id');
this.setState({current_user: user});
}
var user_fix = this.state.current_user;
let player_name = await AsyncStorage.getItem('Player2');
AsyncStorage.setItem('Winner', player_name);
fetch('fetch address is here', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: user_fix,
Name: player_name,
Host: host
})
}).then((response) => response.json())
.then((responseJson) => {
if (host == 'yes') {
this.socket.emit('end_round', 'end');
this.props.navigation.navigate('end_round_host_screen');
} else {
// This is the navigation to the screen getting the error:
this.socket.emit('end_round', 'end');
this.props.navigation.navigate('end_round_guest_screen');
}
}).catch((error) => {
console.error(error);
});
}
render() {
return (
<ScrollView>
<View style={{flexDirection: 'row'}}>
<View style={styles.container}>
<Text style={[styles.text]}>
{this.state.player1}
</Text>
<CardFlip1 />
<PrimaryButton
onPress={() => this.player1Win()}
label={this.state.label1}
>
</PrimaryButton>
</View>
<View style={styles.container}>
<Text style={[styles.text]}>
{this.state.player2}
</Text>
<CardFlip2 />
<PrimaryButton
onPress={() => this.player2Win()}
label={this.state.label2}
>
</PrimaryButton>
</View>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: colors.backgroundColor,
margin: 10,
paddingBottom: 5,
borderWidth: 1,
borderColor: colors.borderColor,
},
text: {
fontSize: 18,
color: colors.primaryText,
marginTop: 10,
},
textPadding: {
paddingBottom: 10,
},
headingText: {
fontSize: 24,
fontWeight: '500',
color: colors.primaryText,
margin: 10,
},
textMarginHorizontal: {
marginHorizontal: 10,
},
})
export default judge_screen;
Here is the code for the "end_round_guest_screen" that I am trying to navigate to:
import React, {Component} from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
import colors from '../config/colors';
import {PrimaryButton} from '../components/PrimaryButton';
import {ScoreBoardGuest} from '../components/ScoreBoardGuest';
import AsyncStorage from '@react-native-community/async-storage';
import Orientation from 'react-native-orientation-locker';
window.navigator.userAgent = 'react-native';
import io from 'socket.io-client/dist/socket.io';
class end_round_guest_screen extends Component {
constructor (props) {
super(props);
this.state = {
game_round: '',
winner: ''
}
}
componentWillMount = () => {
this.getActives();
this.getWinner();
}
componentDidMount() {
Orientation.unlockAllOrientations();
this.socket = io("socket address is here", {
jsonp: false
});
this.socket.on('next_round', () => this.nextRound());
this.socket.on('end_game', () => this.endGame());
}
getActives = async () => {
let round = await AsyncStorage.getItem('Round');
this.setState({game_round: round});
}
getWinner = async () => {
let user = await AsyncStorage.getItem('users_id');
//let host = await AsyncStorage.getItem('host');
fetch('fetch address is here', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application.json',
},
body: JSON.stringify({
email: user
})
}).then((response) => response.json())
.then((responseJson) => {
this.setState({winner: responseJson});
}).catch((error) => {
console.error(error);
});
}
nextRound = () => {
this.props.navigation.navigate('round_start_guest_screen');
}
endGame = () => {
this.props.navigation.navigate('end_game_guest_screen');
}
render() {
return (
<ScrollView>
<View style={{alignItems: 'center'}}>
<Text style={styles.headingText}>
Round {this.state.game_round}
</Text>
<Text style={styles.text}>
{this.state.winner} wins this round!
</Text>
</View>
<View style={styles.container}>
<ScoreBoardGuest />
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
backgroundColor: colors.backgroundColor,
margin: 10,
paddingVertical: 5,
borderWidth: 1,
borderColor: colors.borderColor,
},
text: {
fontSize: 18,
color: colors.primaryText,
marginTop: 10,
},
headingText: {
fontSize: 24,
fontWeight: '500',
color: colors.primaryText,
margin: 10,
},
})
export default end_round_guest_screen;
The "end_round_guest_screen" shows for a second or 2 without any states loaded and then goes to that "Card Back" screen with the error message.
Upvotes: 3
Views: 14183
Reputation: 953
The stacktrace shows that the error is happening inside the end_round_guest_screen
. Based on your description that it is displayed for 2 seconds before navigating to "Card Back" with an error, I assume that it's happening on the line this.setState({winner: responseJson})
inside the fetch callback.
This could happen if the fetch request is still waiting for the response, then either the nextRound or endGame handler got called, which triggered the navigation, and therefore the unmount. By the time fetch got the data, and is about to call setState, the component is no longer mounted.
There are two ways to solve this generally.
1.) (Anti-pattern) Track whether component is still mounted using the componentDidMount/componentWillUnmount callback, then perform the isMounted check before calling setState.
componentDidMount() {
this.isMounted = false
}
componentWillUnmount() {
this.isMounted = false
}
getWinner = async () => {
//inside fetch
if (this.isMounted) {
this.setState({winner: responseJson})
}
}
2.) (Recommended) Cancel any pending network requests in componentWillUnmount callback using.
For example, you can use an AbortController to cancel fetch request. See https://stackoverflow.com/a/53435967/803865 for sample code.
Upvotes: 3