1 votes

"Impossible d'effectuer une mise à jour de l'état React sur un composant non monté" Suivi par "TYPE ERORR : null is not an object" en continu.

L'application sur laquelle je travaille est une application multi-jeux (un jeu d'oiseaux, un jeu de taupes et un jeu de mines). L'écran de démarrage est le menu. Il me donne un avertissement de cycle sur cet écran. Lorsque je tape pour lancer le jeu de la taupe, il joue naturellement, mais dès que j'appuie sur le bouton retour pour revenir au menu, je reçois des erreurs : " Impossible d'effectuer une mise à jour de l'état de React sur un composant non monté" erreur suivie par continue " TYPE ERORR : null n'est pas un objet ".

Captures d'écran :

Avertissement de cycle

Null n'est pas un objet

Erreurs de Node.js

App.js du jeu de la taupe

import {
    View,
    StyleSheet,
    Image,
    SafeAreaView,
    Text,
    TouchableWithoutFeedback
} from 'react-native';
import Images from './assets/Images';
import Constants from './Constants';
import Mole from './Mole';
import GameOver from './GameOver';
import Clear from './Clear';
import Pause from './Pause';

const DEFAULT_TIME = 20;
const DEFAULT_STATE = {
    level: 1,
    score: 0,
    time: DEFAULT_TIME,
    cleared: false,
    paused: false,
    gameover: false,
    health: 100
}

export default class MoleGame extends Component {
    constructor(props) {
        super(props);
        this.moles = [];
        this.state = DEFAULT_STATE;
        this.molesPopping = 0;
        this.interval = null;
        this.timeInterval = null;

    }

    componentDidMount = () => {
        this.setupTicks(DEFAULT_STATE, this.pause);
    }

    setupTicks = () => {
        let speed = 750 - (this.state.level * 50);
        if (speed < 350) {
            speed = 350;
        }
        this.interval = setInterval(this.popRandomMole, speed);
        this.timeInterval = setInterval(this.timerTick, 1000);
    }

    reset = () => {
        this.molesPopping = 0;
        this.setState(DEFAULT_STATE, this.setupTicks)
    }

    pause = () => {
        if (this.interval) clearInterval(this.interval);
        if (this.timeInterval) clearInterval(this.timeInterval);
        this.setState({
            paused: true
        });
    }

    resume = () => {
        this.molesPopping = 0;
        this.setState({
            paused: false
        }, this.setupTicks);
    }

    nextLevel = () => {
        this.molesPopping = 0;

        this.setState({
            level: this.state.level + 1,
            cleared: false,
            gameover: false,
            time: DEFAULT_TIME
        }, this.setupTicks)
    }

    timerTick = () => {
        if (this.state.time === 0) {
            clearInterval(this.interval);
            clearInterval(this.timeInterval);
            this.setState({
                cleared: true
            })
        } else {
            this.setState({
                time: this.state.time - 1
            })
        }
    }

    gameOver = () => {
        clearInterval(this.interval);
        clearInterval(this.timerInterval);
        this.setState({
            gameover: true
        })

    }

    onDamage = () => {
        if (this.state.cleared || this.state.gameOver || this.state.paused) {
            return;
        }
        let targetHealth = this.state.health - 10 < 0 ? 0 : this.state.health - 20;

        this.setState({
            health: targetHealth
        });

        if (targetHealth <= 0) {
            this.gameOver();
        }
    }

    onHeal = () => {
        let targetHealth = this.state.health + 10 > 100 ? 100 : this.state.health + 10
        this.setState({
            health: targetHealth
        })
    }

    onScore = () => {
        this.setState({
            score: this.state.score + 1
        })
    }

    randomBetween = (min, max) => {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    onFinishPopping = (index) => {
        this.molesPopping -= 1;
    }

    popRandomMole = () => {
        if (this.moles.length != 12) {
            return;
        }
        let randomIndex = this.randomBetween(0, 11);
        if (!this.moles[randomIndex].isPopping && this.molesPopping < 3) {
            this.molesPopping += 1;
            this.moles[randomIndex].pop();
        }
    }

    render() {
        let healthBarWidth = (Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60 - Constants.XR * 6) * this.state.health / 100;
        return (
            <View style={styles.container}>
                <Image style={styles.backgroundImage} resizeMode="stretch" source={Images.background} />
                <View style={styles.topPanel}>
                    <SafeAreaView>
                        <View style={styles.statsContainer}>
                            <View style={styles.stats}>
                                <View style={styles.levelContainer}>
                                    <Text style={styles.levelTitle}>Level</Text>
                                    <Text style={styles.levelNumber}>{this.state.level}</Text>
                                </View>
                            </View>
                            <View style={styles.stats}>
                                <View style={styles.timeBar}>
                                    <Text style={styles.timeNumber}>{this.state.time}</Text>
                                </View>
                                <Image style={styles.timeIcon} resizeMode="stretch" source={Images.timeIcon} />
                            </View>
                            <View style={styles.stats}>
                                <View style={styles.scoreBar}>
                                    <Text style={styles.scoreNumber}>{this.state.score}</Text>
                                </View>
                                <Image style={styles.scoreIcon} resizeMode="stretch" source={Images.scoreIcon} />
                            </View>
                            <View style={styles.stats}>
                                <TouchableWithoutFeedback onPress={this.pause}>
                                    <View style={styles.pauseButton}>
                                        <Image style={styles.pauseButtonIcon} resizeMode="stretch" source={Images.pauseIcon} />
                                    </View>
                                </TouchableWithoutFeedback>
                            </View>
                        </View>

                        <View style={styles.healthBarContainer}>
                            <View style={styles.healthBar}>
                                <View style={[styles.healthBarInner, { width: healthBarWidth }]} />
                            </View>
                            <Image style={styles.healthIcon} resizeMode="stretch" source={Images.healthIcon} />
                        </View>
                    </SafeAreaView>
                </View>
                <View style={styles.playArea}>
                    {Array.apply(null, Array(4)).map((el, rowIdx) => {
                        return (
                            <View style={styles.playRow} key={rowIdx}>
                                {Array.apply(null, Array(3)).map((el, colIdx) => {
                                    let moleIdx = (rowIdx * 3) + colIdx;

                                    return (
                                        <View style={styles.playCell} key={colIdx}>
                                            <Mole
                                                index={moleIdx}
                                                onDamage={this.onDamage}
                                                onHeal={this.onHeal}
                                                onFinishPopping={this.onFinishPopping}
                                                onScore={this.onScore}
                                                ref={(ref) => { this.moles[moleIdx] = ref }}
                                            />
                                        </View>
                                    )
                                })}
                            </View>
                        )
                    })}
                </View>
                {this.state.cleared && <Clear onReset={this.reset} onNextLevel={this.nextLevel} level={this.state.level} score={this.state.score} />}
                {this.state.gameover && <GameOver onReset={this.reset} level={this.state.level} score={this.state.score} />}
                {this.state.paused && <Pause onReset={this.reset} onResume={this.resume} />}
            </View>
        )
    }
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        flexDirection: 'column'
    },
    backgroundImage: {
        width: Constants.MAX_WIDTH,
        height: Constants.MAX_HEIGHT,
        position: 'absolute'
    },
    topPanel: {
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        height: Constants.YR * 250,
        flexDirection: 'column'
    },
    statsContainer: {
        width: Constants.MAX_WIDTH,
        height: Constants.YR * 120,
        flexDirection: 'row'
    },
    stats: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    },
    pauseButton: {
        width: Constants.YR * 50,
        height: Constants.YR * 50,
        backgroundColor: 'black',
        borderColor: 'white',
        borderWidth: 3,
        borderRadius: 10,
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center'
    },
    pauseButtonIcon: {
        width: Constants.YR * 25,
        height: Constants.YR * 25,
    },
    levelContainer: {
        width: Constants.YR * 80,
        height: Constants.YR * 80,
        backgroundColor: '#ff1a1a',
        borderColor: 'white',
        borderWidth: 3,
        borderRadius: 10,
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center'
    },
    levelTitle: {
        fontSize: 21,
        color: 'white',
        fontFamily: 'LilitaOne'
    },
    levelNumber: {
        fontSize: 17,
        color: 'white',
        fontFamily: 'LilitaOne'
    },
    scoreIcon: {
        position: 'absolute',
        left: 0,
        width: Constants.YR * 40,
        height: Constants.YR * 40,
    },
    scoreBar: {
        height: Constants.YR * 25,
        position: 'absolute',
        left: 20,
        right: 5,
        backgroundColor: 'white',
        borderRadius: 13,
        justifyContent: 'center',
        alignItems: 'center'
    },
    scoreNumber: {
        fontSize: 17,
        color: 'black',
        fontFamily: 'LilitaOne',
    },
    timeIcon: {
        position: 'absolute',
        left: 0,
        width: Constants.YR * 40,
        height: Constants.YR * 40,
    },
    timeBar: {
        height: Constants.YR * 25,
        position: 'absolute',
        left: 20,
        right: 5,
        backgroundColor: 'white',
        borderRadius: 13,
        justifyContent: 'center',
        alignItems: 'center'
    },
    timeNumber: {
        fontSize: 17,
        color: 'black',
        fontFamily: 'LilitaOne',
    },
    healthBarContainer: {
        height: Constants.YR * 40,
        width: Constants.MAX_WIDTH - Constants.XR * 120,
        marginLeft: Constants.XR * 60
    },
    healthIcon: {
        position: 'absolute',
        top: 0,
        left: 0,
        width: Constants.YR * 46,
        height: Constants.YR * 40,
    },
    healthBar: {
        height: Constants.YR * 20,
        width: Constants.MAX_WIDTH - Constants.XR * 100 - Constants.XR * 60,
        marginLeft: Constants.XR * 40,
        marginTop: Constants.YR * 10,
        backgroundColor: 'white',
        borderRadius: Constants.YR * 10
    },
    healthBarInner: {
        position: 'absolute',
        backgroundColor: '#ff1a1a',
        left: Constants.XR * 3,

        top: Constants.YR * 3,
        bottom: Constants.YR * 3,
        borderRadius: Constants.YR * 8
    },
    playArea: {
        width: Constants.MAX_WIDTH,
        marginTop: Constants.YR * 250,
        height: Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112,
        flexDirection: 'column',
    },
    playRow: {
        height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
        width: Constants.MAX_WIDTH,
        flexDirection: 'row',
    },
    playCell: {
        width: Constants.MAX_WIDTH / 3,
        height: (Constants.MAX_HEIGHT - Constants.YR * 250 - Constants.YR * 112) / 4,
        alignItems: 'center'
    }
});

Mole.js

import { View, StyleSheet, Button, Image, TouchableWithoutFeedback } from 'react-native';
import Images from './assets/Images';
import SpriteSheet from 'rn-sprite-sheet';
//import Constants from './Constants';

export default class Mole extends Component {
    constructor (props) {
        super(props);
        this.mole = null;
        this.actionTimeout = null;
        this.isPopping = false;
        this.isWacked = false; 
        this.isHealing = false; 
        this.isAttacking = false; 
        this.isFeisty = false; 
    }

    pop =()=>{
        this.isWacked = false;
        this.isPopping = true;
        this.isAttacking = false; 

        this.isFeisty = Math.random() < 0.4;
        if(!this.isFeisty){
            this.isHealing = Math.random() < 0.12;
        }

        if(this.isHealing){
            this.mole.play({
                type: "heal",
                onFinish :  ()=>{
                    this.actionTimeout = setTimeout(()=>{
                        this.mole.play({
                            type : "hide",
                            fps: 24, 
                            onFinish: ()=>{
                                this.isPopping = false; 
                                this.props.onFinishPopping(this.props.index);
                            }
                        })
                    }, 1000); 
                }
            })
        }
        else{
            this.mole.play({
                type : "appear",
                fps: 24,
                onFinish: ()=>{
                    if (this.isFeisty){ 
                        this.actionTimeout = setTimeout(() => { 
                            this.isAttacking = true;    
                            this.props.onDamage();  
                            this.mole.play({    
                                type: "attack", 
                                fps: 12,    
                                onFinish: () => {   
                                    this.mole.play({    
                                        type: "hide",   
                                        fps: 24,    
                                        onFinish: () => {   
                                            this.isPopping = false; 
                                            this.props.onFinishPopping(this.props.index);   
                                        }   
                                    })  
                                }   
                            })  
                        }, 1000)
                    }
                    else{
                        this.actionTimeout = setTimeout(()=>{
                            this.mole.play({
                                type : "hide",
                                fps: 24, 
                                onFinish: ()=>{
                                    this.isPopping = false; 
                                    this.props.onFinishPopping(this.props.index);
                                }
                            })
                        }, 1000);
                    }

                }
            })
        }
    }

    whack = ()=>{
        if(!this.isPopping || this.isWacked || this.isAttacking){
            return;
        }
        if (this.actionTimeout){
            clearTimeout(this.actionTimeout);
        }
        this.isWacked = true;

        this.props.onScore ();
        if( this.isHealing){
            this.props.onHeal();
        }
        this.mole.play({
            type: "dizzy",
            fps: 24,
            onFinish: () => {
                this.mole.play({
                    type: "faint",
                    fps: 24,
                    onFinish: () => {
                        this.isPopping = false;
                        this.props.onFinishPopping(this.props.index);
                    }
                })
            }
        })
    }

    render() {
        return (
            <View style= {styles.container}>
                <SpriteSheet  
                ref= {ref=> {this.mole = ref}}
                source = {Images.sprites}
                columns = {6}
                rows = {8}
                width = {100} 
                animations = {{
                    idle: [0],
                    appear: [1,2,3,4],
                    hide: [4,3,2,1,0],
                    dizzy : [36,37,38],
                    faint: [42,43,44,0],
                    attack: [11,12,13,14,15,16],
                    heal: [24,25,26,27,28,29,30,31,32,33]
                }} />
                <TouchableWithoutFeedback onPress= {this.whack} style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }}>
                    <View style= {{position: 'absolute', top: 0 , bottom:0, left:0, right:0 }} />
                </TouchableWithoutFeedback>  
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1
    }
})

index.js (Navigation)

import React from "react";
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import MoleGame from './Game1/App.js'
import BirdGame from './Game2/App.js'
import HomeScreen from './Game3/screens/HomeScreen.js'
import GameScreen from './Game3/screens/GameScreen.js'
import Home from './Home.js'

const Stack = createStackNavigator();

export default App = () => {
    return (
        <NavigationContainer>
            <Stack.Navigator screenOptions={{ headerShown: false, gestureEnabled: false, }}  >
                <Stack.Screen name="Home" component={Home} />
                <Stack.Screen name="MoleGame" component={MoleGame} />
                <Stack.Screen name="BirdGame" component={BirdGame} />
                <Stack.Screen name="MineSweeperHome" component={HomeScreen} />
                <Stack.Screen name="MineSweeperGame" component={GameScreen} />
            </Stack.Navigator>
        </NavigationContainer>
    );
};

Home.js

import { View, Text, TouchableOpacity, Dimensions, StyleSheet } from 'react-native';

const GameButton = ({ label, onPress, style }) => {
    return (
        <TouchableOpacity
            style={style}
            onPress={onPress}
        >
            <Text style={styles.text}>{label}</Text>
        </TouchableOpacity>
    );
};

export default Home = ({ navigation }) => {
    return (
        <View style={styles.container}>
            <View style={styles.welcome}>
                <Text style={styles.text}> Welcome! </Text>
            </View>
            <View style={styles.buttonContainer}>
                <GameButton
                    label={'Play Bird Game'}
                    onPress={() => navigation.navigate('BirdGame')}
                    style={styles.gameButton}
                />
                <GameButton
                    label={'Play Mole Game'}
                    onPress={() => navigation.navigate('MoleGame')}
                    style={{ ...styles.gameButton, backgroundColor: 'green' }}
                />
                <GameButton
                    label={'Play MineSweeper Game'}
                    onPress={() => navigation.navigate('MineSweeperHome')}
                    style={{ ...styles.gameButton, backgroundColor: 'grey' }}
                />
            </View>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#FFCB43'
    },
    welcome: {
        alignItems: 'center',
        justifyContent: 'center',
        padding: 10,
        marginTop: 80,
    },
    buttonContainer: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'space-evenly'
    },
    gameButton: {
        height: 70,
        width: Dimensions.get("screen").width / 1.4,
        backgroundColor: 'red',
        borderColor: 'red',
        alignItems: 'center',
        justifyContent: 'center',
        borderRadius: 20,
        borderWidth: 2,
        borderColor: 'black',
    },
    text: {
        fontSize: 22,
    }
})

Quelques contextes et avis

  • Le projet a été lancé sur la machine Mac de mon ami. Selon lui, il ne reçoit pas ces erreurs. Et il a également été en mesure de construire l'application dans un .apk pour Android.
  • Nous utilisons tous deux la même version de node.js (15.11.0).
  • Il y a des moments où mon métro ne fonctionne pas, et quand cela arrive, l'application fonctionne sans aucune erreur.
  • J'ai essayé de le construire dans un .apk, mais la construction a échoué.
  • Avant que l'application ne soit combinée en 3 jeux. Il s'agissait de jeux individuels. Le jeu de la taupe a fonctionné et a été intégré avec succès dans un .apk.
  • Le jeu de la taupe a été créé à partir de ici Le code est à peu près le même.

UPDATE :

Après avoir ajouté

componentWillUnmount(){
        clearInterval(this.interval);
        clearInterval(this.timerInterval);
    }

à l'App.js. Je n'obtiens plus en permanence "TypeError : null is not an object (evaluating '_this.moles[randomIndex].isPopping')". Malheureusement, j'obtiens toujours l'erreur TypeError : null is not an object (evaluating '_this.moles.play). Cela se produit lorsque je retourne au menu principal. J'ai essayé d'ajouter clearTimeout(this.actionTimeout) sur mole.js, mais cela n'a donné aucun effet.

Capture d'écran :

TypeError : null n'est pas un objet (évaluation de '_this.moles.play)

1voto

ksav Points 3880

El setInterval() est généralement utilisée pour définir un délai pour les fonctions qui sont exécutées à plusieurs reprises, comme les animations. Vous pouvez annuler l'intervalle en utilisant clearInterval() .

Lorsque vous naviguez à partir de MoleGame a Home le MoleGame est retiré de la pile de navigation et son composant est démonté. Mais les intervalles de la setupTicks sont toujours en cours d'exécution, et on essaie de définir l'état de la méthode MoleGame et d'accéder à this (aucun des deux n'est possible).

Essayez clearInterval en componentWillUnmount pour arrêter les intervalles qui sont définis dans les setupTicks méthode.

// App.js
export default class MoleGame extends Component {
    constructor(props) {
        super(props);
        this.interval = null;
        this.timeInterval = null;
        ...
    }

    componentDidMount = () => {
        this.setupTicks(DEFAULT_STATE, this.pause);
    }

    componentWillUnmount() {
        clearInterval(this.interval);
        clearInterval(this.timeInterval);
    }
}

De même, dans Mole.js vous auriez à gérer le même scénario pour toute setTimeout() que vous avez en utilisant clearTimeout()


Pour en savoir plus : Colmater les fuites de mémoire dans votre application

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X