【问题标题】:React-Native: Warning: Can't perform a React state update on an unmounted componentReact-Native:警告:无法对未安装的组件执行 React 状态更新
【发布时间】:2019-09-11 03:37:29
【问题描述】:

当我尝试从一个屏幕转换到另一个屏幕时收到以下错误消息:

这发生在一个游戏应用程序中,其中多部手机参与游戏,并且根据它们在游戏中的角色以及该手机是托管游戏还是访客而具有不同的屏幕。

当我试图到达下一个屏幕(在左侧手机上)时,下图显示了此错误消息。左边手机的屏幕应该和右边的一样,但没有“下一轮”和“结束游戏”按钮。但我得到一个完全不同的屏幕,显示错误消息:

这是前一个屏幕的代码,应该将手机导航到这个“分数”屏幕:

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;

这是我试图导航到的“end_round_guest_screen”的代码:

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;

“end_round_guest_screen”会显示一两秒,但没有加载任何状态,然后会转到带有错误消息的“Card Back”屏幕。

【问题讨论】:

  • 尝试一些中间件或在componentDidMount中做所有事情
  • 在 componentDidMount 中做所有事情都不起作用,你能推荐任何特定的中间件吗?
  • redux-thunk 如果你不怕回调,redux-observable 如果你知道 rxjs(无论如何值得知道)。

标签: javascript react-native socket.io


【解决方案1】:

堆栈跟踪显示错误发生在end_round_guest_screen 内部。根据您的描述,它在导航到“卡回”之前显示 2 秒并出现错误,我假设它发生在 fetch 回调内的 this.setState({winner: responseJson}) 行上。

如果获取请求仍在等待响应,则可能会发生这种情况,然后调用 nextRound 或 endGame 处理程序,这会触发导航,从而触发卸载。当 fetch 得到数据,并且即将调用 setState 时,组件不再挂载。

一般有两种方法可以解决。

1.)(反模式)通过componentDidMount/componentWillUnmount回调跟踪组件是否还在挂载,然后在调用setState之前进行isMounted检查。

componentDidMount() {
    this.isMounted = false
}

componentWillUnmount() {
    this.isMounted = false
}

getWinner = async () => {
    //inside fetch
    if (this.isMounted) {
        this.setState({winner: responseJson})
    }
}

2.)(推荐)在 componentWillUnmount 回调中使用取消任何挂起的网络请求。

例如,您可以使用 AbortController 取消获取请求。有关示例代码,请参阅https://stackoverflow.com/a/53435967/803865。

【讨论】:

  • #1 是一种反模式,因为 isMounted 在检查后但在设置状态之前可能会变为假?
猜你喜欢
  • 2021-08-07
  • 2020-06-14
  • 1970-01-01
  • 2021-03-27
  • 2021-02-24
  • 1970-01-01
  • 2021-11-30
  • 2020-09-03
相关资源
最近更新 更多